diff options
author | Hans-Christoph Steiner <eighthave@users.sourceforge.net> | 2011-10-09 16:36:37 +0000 |
---|---|---|
committer | Hans-Christoph Steiner <eighthave@users.sourceforge.net> | 2011-10-09 16:36:37 +0000 |
commit | 21c068f1916330e90f814bed461fe0821d1665ec (patch) | |
tree | 949b73696fff09a44b8d3eb01b70bae7174cbd14 /pd/portaudio/src/hostapi/asio | |
parent | bf8ced1efe1a032342e864edc635fa4e2676670d (diff) |
checked in pd-0.43-0.src.tar.gz
svn path=/trunk/; revision=15557
Diffstat (limited to 'pd/portaudio/src/hostapi/asio')
-rw-r--r-- | pd/portaudio/src/hostapi/asio/pa_asio.cpp | 1538 |
1 files changed, 1290 insertions, 248 deletions
diff --git a/pd/portaudio/src/hostapi/asio/pa_asio.cpp b/pd/portaudio/src/hostapi/asio/pa_asio.cpp index 4b3fb68e..84d1c511 100644 --- a/pd/portaudio/src/hostapi/asio/pa_asio.cpp +++ b/pd/portaudio/src/hostapi/asio/pa_asio.cpp @@ -1,10 +1,12 @@ /* - * $Id: pa_asio.cpp 1230 2007-06-15 16:16:33Z rossb $ + * $Id: pa_asio.cpp 1416 2009-06-16 16:12:41Z rossb $ * Portable Audio I/O Library for ASIO Drivers * * Author: Stephane Letz * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 2000-2002 Stephane Letz, Phil Burk, Ross Bencina + * Blocking i/o implementation by Sven Fischer, Institute of Hearing + * Technology and Audiology (www.hoertechnik-audiologie.de) * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files @@ -71,18 +73,18 @@ */ /** @file - @ingroup hostapi_src + @ingroup hostapi_src Note that specific support for paInputUnderflow, paOutputOverflow and paNeverDropInput is not necessary or possible with this driver due to the synchronous full duplex double-buffered architecture of ASIO. - @todo check that CoInitialize()/CoUninitialize() are always correctly - paired, even in error cases. - @todo implement host api specific extension to set i/o buffer sizes in frames - @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + @todo review ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + + @todo review Blocking i/o latency computations in OpenStream(), changing ring + buffer to a non-power-of-two structure could reduce blocking i/o latency. @todo implement IsFormatSupported @@ -120,6 +122,7 @@ #include <assert.h> #include <string.h> //#include <values.h> +#include <new> #include <windows.h> #include <mmsystem.h> @@ -133,6 +136,7 @@ #include "pa_cpuload.h" #include "pa_process.h" #include "pa_debugprint.h" +#include "pa_ringbuffer.h" /* This version of pa_asio.cpp is currently only targetted at Win32, It would require a few tweaks to work with pre-OS X Macintosh. @@ -164,16 +168,24 @@ #endif */ -/* external references */ -extern AsioDrivers* asioDrivers ; -bool loadAsioDriver(char *name); +/* external reference to ASIO SDK's asioDrivers. -/* We are trying to be compatible with CARBON but this has not been thoroughly tested. */ -/* not tested at all since new code was introduced. */ -#define CARBON_COMPATIBLE (0) + This is a bit messy because we want to explicitly manage + allocation/deallocation of this structure, but some layers of the SDK + which we currently use (eg the implementation in asio.cpp) still + use this global version. + + For now we keep it in sync with our local instance in the host + API representation structure, but later we should be able to remove + all dependence on it. +*/ +extern AsioDrivers* asioDrivers; +/* We are trying to be compatible with CARBON but this has not been thoroughly tested. */ +/* not tested at all since new V19 code was introduced. */ +#define CARBON_COMPATIBLE (0) /* prototypes for functions declared in this file */ @@ -206,6 +218,14 @@ static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long static signed long GetStreamReadAvailable( PaStream* stream ); static signed long GetStreamWriteAvailable( PaStream* stream ); +/* Blocking i/o callback function. */ +static int BlockingIoPaCallback(const void *inputBuffer , + void *outputBuffer , + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo , + PaStreamCallbackFlags statusFlags , + void *userData ); + /* our ASIO callback functions */ static void bufferSwitch(long index, ASIOBool processNow); @@ -270,12 +290,12 @@ static const char* PaAsio_GetAsioErrorText( ASIOError asioError ) // Atomic increment and decrement operations #if MAC - /* need to be implemented on Mac */ - inline long PaAsio_AtomicIncrement(volatile long* v) {return ++(*const_cast<long*>(v));} - inline long PaAsio_AtomicDecrement(volatile long* v) {return --(*const_cast<long*>(v));} + /* 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));} + 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 @@ -300,6 +320,7 @@ typedef struct PaUtilAllocationGroup *allocations; + AsioDrivers *asioDrivers; void *systemSpecific; /* the ASIO C API only allows one ASIO driver to be open at a time, @@ -323,7 +344,7 @@ PaAsioHostApiRepresentation; Retrieve <driverCount> driver names from ASIO, returned in a char** allocated in <group>. */ -static char **GetAsioDriverNames( PaUtilAllocationGroup *group, long driverCount ) +static char **GetAsioDriverNames( PaAsioHostApiRepresentation *asioHostApi, PaUtilAllocationGroup *group, long driverCount ) { char **result = 0; int i; @@ -341,7 +362,7 @@ static char **GetAsioDriverNames( PaUtilAllocationGroup *group, long driverCount for( i=0; i<driverCount; ++i ) result[i] = result[0] + (32 * i); - asioDrivers->getDriverNames( result, driverCount ); + asioHostApi->asioDrivers->getDriverNames( result, driverCount ); error: return result; @@ -917,7 +938,7 @@ PaAsioDeviceInfo; PaError PaAsio_GetAvailableLatencyValues( PaDeviceIndex device, - long *minLatency, long *maxLatency, long *preferredLatency, long *granularity ) + long *minLatency, long *maxLatency, long *preferredLatency, long *granularity ) { PaError result; PaUtilHostApiRepresentation *hostApi; @@ -944,23 +965,45 @@ PaError PaAsio_GetAvailableLatencyValues( PaDeviceIndex device, return result; } - +/* Unload whatever we loaded in LoadAsioDriver(). + Also balance the call to CoInitialize(0). +*/ +static void UnloadAsioDriver( void ) +{ + ASIOExit(); + CoUninitialize(); +} /* load the asio driver named by <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. + and must be closed by the called by calling UnloadAsioDriver() - if an error + is returned the driver will already be unloaded. */ -static PaError LoadAsioDriver( const char *driverName, +static PaError LoadAsioDriver( PaAsioHostApiRepresentation *asioHostApi, const char *driverName, PaAsioDriverInfo *driverInfo, void *systemSpecific ) { PaError result = paNoError; ASIOError asioError; int asioIsInitialized = 0; - if( !loadAsioDriver( const_cast<char*>(driverName) ) ) + /* + ASIO uses CoCreateInstance() to load a driver. That requires that + CoInitialize(0) be called for every thread that loads a driver. + It is OK to call CoInitialize(0) multiple times form one thread as long + as it is balanced by a call to CoUninitialize(). See UnloadAsioDriver(). + + The V18 version called CoInitialize() starting on 2/19/02. + That was removed from PA V19 for unknown reasons. + Phil Burk added it back on 6/27/08 so that JSyn would work. + */ + CoInitialize( 0 ); + + if( !asioHostApi->asioDrivers->loadDriver( const_cast<char*>(driverName) ) ) { + /* If this returns an error then it might be because CoInitialize(0) was removed. + It should be called right before this. + */ result = paUnanticipatedHostError; PA_ASIO_SET_LAST_HOST_ERROR( 0, "Failed to load ASIO driver" ); goto error; @@ -1006,8 +1049,10 @@ static PaError LoadAsioDriver( const char *driverName, error: if( asioIsInitialized ) - ASIOExit(); - + { + ASIOExit(); + } + CoUninitialize(); return result; } @@ -1039,6 +1084,8 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex goto error; } + asioHostApi->asioDrivers = 0; /* avoid surprises in our error handler below */ + asioHostApi->allocations = PaUtil_CreateAllocationGroup(); if( !asioHostApi->allocations ) { @@ -1046,6 +1093,25 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex goto error; } + /* Allocate the AsioDrivers() driver list (class from ASIO SDK) */ + try + { + asioHostApi->asioDrivers = new AsioDrivers(); /* calls CoInitialize(0) */ + } + catch (std::bad_alloc) + { + asioHostApi->asioDrivers = 0; + } + /* some implementations of new (ie MSVC, see http://support.microsoft.com/?kbid=167733) + don't throw std::bad_alloc, so we also explicitly test for a null return. */ + if( asioHostApi->asioDrivers == 0 ) + { + result = paInsufficientMemory; + goto error; + } + + asioDrivers = asioHostApi->asioDrivers; /* keep SDK global in sync until we stop depending on it */ + asioHostApi->systemSpecific = 0; asioHostApi->openAsioDeviceIndex = paNoDevice; @@ -1059,23 +1125,19 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex #ifdef WINDOWS /* use desktop window as system specific ptr */ asioHostApi->systemSpecific = GetDesktopWindow(); - CoInitialize(NULL); #endif - /* MUST BE CHECKED : to force fragments loading on Mac */ - loadAsioDriver( "dummy" ); - /* driverCount is the number of installed drivers - not necessarily the number of installed physical devices. */ #if MAC - driverCount = asioDrivers->getNumFragments(); + driverCount = asioHostApi->asioDrivers->getNumFragments(); #elif WINDOWS - driverCount = asioDrivers->asioGetNumDev(); + driverCount = asioHostApi->asioDrivers->asioGetNumDev(); #endif if( driverCount > 0 ) { - names = GetAsioDriverNames( asioHostApi->allocations, driverCount ); + names = GetAsioDriverNames( asioHostApi, asioHostApi->allocations, driverCount ); if( !names ) { result = paInsufficientMemory; @@ -1102,7 +1164,7 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex goto error; } - IsDebuggerPresent_ = GetProcAddress( LoadLibrary( "Kernel32.dll" ), "IsDebuggerPresent" ); + IsDebuggerPresent_ = GetProcAddress( LoadLibrary( "Kernel32.dll" ), "IsDebuggerPresent" ); for( i=0; i < driverCount; ++i ) { @@ -1120,7 +1182,6 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex || strcmp (names[i],"ASIO Multimedia Driver") == 0 || strncmp(names[i],"Premiere",8) == 0 //"Premiere Elements Windows Sound 1.0" || strncmp(names[i],"Adobe",5) == 0 //"Adobe Default Windows Sound 1.5" - || strncmp(names[i],"ReaRoute ASIO",13) == 0 //Reaper www.reaper.fm <- fix your stuff man. ) { PA_DEBUG(("BLACKLISTED!!!\n")); @@ -1141,7 +1202,7 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex /* Attempt to load the asio driver... */ - if( LoadAsioDriver( names[i], &paAsioDriverInfo, asioHostApi->systemSpecific ) == paNoError ) + if( LoadAsioDriver( asioHostApi, names[i], &paAsioDriverInfo, asioHostApi->systemSpecific ) == paNoError ) { PaAsioDeviceInfo *asioDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ]; PaDeviceInfo *deviceInfo = &asioDeviceInfo->commonDeviceInfo; @@ -1233,7 +1294,7 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex if( !asioDeviceInfo->asioChannelInfos ) { result = paInsufficientMemory; - goto error; + goto error_unload; } int a; @@ -1246,7 +1307,7 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex { result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); - goto error; + goto error_unload; } } @@ -1259,13 +1320,13 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex { result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); - goto error; + goto error_unload; } } /* unload the driver */ - ASIOExit(); + UnloadAsioDriver(); (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo; ++(*hostApi)->info.deviceCount; @@ -1302,6 +1363,9 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex return result; +error_unload: + UnloadAsioDriver(); + error: if( asioHostApi ) { @@ -1311,6 +1375,9 @@ error: PaUtil_DestroyAllocationGroup( asioHostApi->allocations ); } + delete asioHostApi->asioDrivers; + asioDrivers = 0; /* keep SDK global in sync until we stop depending on it */ + PaUtil_FreeMemory( asioHostApi ); } return result; @@ -1323,7 +1390,7 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) /* IMPLEMENT ME: - - clean up any resources not handled by the allocation group + - clean up any resources not handled by the allocation group (need to review if there are any) */ if( asioHostApi->allocations ) @@ -1332,6 +1399,9 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) PaUtil_DestroyAllocationGroup( asioHostApi->allocations ); } + delete asioHostApi->asioDrivers; /* calls CoUninitialize() */ + asioDrivers = 0; /* keep SDK global in sync until we stop depending on it */ + PaUtil_FreeMemory( asioHostApi ); } @@ -1418,7 +1488,7 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, /* if an ASIO device is open we can only get format information for the currently open device */ if( asioHostApi->openAsioDeviceIndex != paNoDevice - && asioHostApi->openAsioDeviceIndex != asioDeviceIndex ) + && asioHostApi->openAsioDeviceIndex != asioDeviceIndex ) { return paDeviceUnavailable; } @@ -1430,7 +1500,7 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, /* open the device if it's not already open */ if( asioHostApi->openAsioDeviceIndex == paNoDevice ) { - result = LoadAsioDriver( asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, + result = LoadAsioDriver( asioHostApi, asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, driverInfo, asioHostApi->systemSpecific ); if( result != paNoError ) return result; @@ -1468,7 +1538,7 @@ done: /* close the device if it wasn't already open */ if( asioHostApi->openAsioDeviceIndex == paNoDevice ) { - ASIOExit(); /* not sure if we should check for errors here */ + UnloadAsioDriver(); /* not sure if we should check for errors here */ } if( result == paNoError ) @@ -1479,6 +1549,40 @@ done: +/** A data structure specifically for storing blocking i/o related data. */ +typedef struct PaAsioStreamBlockingState +{ + int stopFlag; /**< Flag indicating that block processing is to be stopped. */ + + unsigned long writeBuffersRequested; /**< The number of available output buffers, requested by the #WriteStream() function. */ + unsigned long readFramesRequested; /**< The number of available input frames, requested by the #ReadStream() function. */ + + int writeBuffersRequestedFlag; /**< Flag to indicate that #WriteStream() has requested more output buffers to be available. */ + int readFramesRequestedFlag; /**< Flag to indicate that #ReadStream() requires more input frames to be available. */ + + HANDLE writeBuffersReadyEvent; /**< Event to signal that requested output buffers are available. */ + HANDLE readFramesReadyEvent; /**< Event to signal that requested input frames are available. */ + + void *writeRingBufferData; /**< The actual ring buffer memory, used by the output ring buffer. */ + void *readRingBufferData; /**< The actual ring buffer memory, used by the input ring buffer. */ + + PaUtilRingBuffer writeRingBuffer; /**< Frame-aligned blocking i/o ring buffer to store output data (interleaved user format). */ + PaUtilRingBuffer readRingBuffer; /**< Frame-aligned blocking i/o ring buffer to store input data (interleaved user format). */ + + long writeRingBufferInitialFrames; /**< The initial number of silent frames within the output ring buffer. */ + + const void **writeStreamBuffer; /**< Temp buffer, used by #WriteStream() for handling non-interleaved data. */ + void **readStreamBuffer; /**< Temp buffer, used by #ReadStream() for handling non-interleaved data. */ + + PaUtilBufferProcessor bufferProcessor; /**< Buffer processor, used to handle the blocking i/o ring buffers. */ + + int outputUnderflowFlag; /**< Flag to signal an output underflow from within the callback function. */ + int inputOverflowFlag; /**< Flag to signal an input overflow from within the callback function. */ +} +PaAsioStreamBlockingState; + + + /* PaAsioStream - a stream data structure specifically for this implementation */ typedef struct PaAsioStream @@ -1515,6 +1619,7 @@ typedef struct PaAsioStream HANDLE completedBuffersPlayedEvent; bool streamFinishedCallbackCalled; + int isStopped; volatile int isActive; volatile bool zeroOutput; /* all future calls to the callback will output silence */ @@ -1522,6 +1627,8 @@ typedef struct PaAsioStream volatile long reenterError; PaStreamCallbackFlags callbackFlags; + + PaAsioStreamBlockingState *blockingState; /**< Blocking i/o data struct, or NULL when using callback interface. */ } PaAsioStream; @@ -1621,15 +1728,15 @@ static PaError ValidateAsioSpecificStreamInfo( int deviceChannelCount, int **channelSelectors ) { - if( streamInfo ) - { - if( streamInfo->size != sizeof( PaAsioStreamInfo ) - || streamInfo->version != 1 ) - { - return paIncompatibleHostApiSpecificStreamInfo; - } + if( streamInfo ) + { + if( streamInfo->size != sizeof( PaAsioStreamInfo ) + || streamInfo->version != 1 ) + { + return paIncompatibleHostApiSpecificStreamInfo; + } - if( streamInfo->flags & paAsioUseChannelSelectors ) + if( streamInfo->flags & paAsioUseChannelSelectors ) *channelSelectors = streamInfo->channelSelectors; if( !(*channelSelectors) ) @@ -1641,9 +1748,101 @@ static PaError ValidateAsioSpecificStreamInfo( return paInvalidChannelCount; } } - } + } + + return paNoError; +} + + +static bool IsUsingExternalClockSource() +{ + bool result = false; + ASIOError asioError; + ASIOClockSource clocks[32]; + long numSources=32; - return paNoError; + /* davidv: listing ASIO Clock sources. there is an ongoing investigation by + me about whether or not to call ASIOSetSampleRate if an external Clock is + used. A few drivers expected different things here */ + + asioError = ASIOGetClockSources(clocks, &numSources); + if( asioError != ASE_OK ){ + PA_DEBUG(("ERROR: ASIOGetClockSources: %s\n", PaAsio_GetAsioErrorText(asioError) )); + }else{ + PA_DEBUG(("INFO ASIOGetClockSources listing %d clocks\n", numSources )); + for (int i=0;i<numSources;++i){ + PA_DEBUG(("ASIOClockSource%d %s current:%d\n", i, clocks[i].name, clocks[i].isCurrentSource )); + + if (clocks[i].isCurrentSource) + result = true; + } + } + + return result; +} + + +static PaError ValidateAndSetSampleRate( double sampleRate ) +{ + PaError result = paNoError; + ASIOError asioError; + + // check that the device supports the requested sample rate + + asioError = ASIOCanSampleRate( sampleRate ); + PA_DEBUG(("ASIOCanSampleRate(%f):%d\n", sampleRate, asioError )); + + if( asioError != ASE_OK ) + { + result = paInvalidSampleRate; + PA_DEBUG(("ERROR: ASIOCanSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); + goto error; + } + + // retrieve the current sample rate, we only change to the requested + // sample rate if the device is not already in that rate. + + ASIOSampleRate oldRate; + asioError = ASIOGetSampleRate(&oldRate); + if( asioError != ASE_OK ) + { + result = paInvalidSampleRate; + PA_DEBUG(("ERROR: ASIOGetSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); + goto error; + } + PA_DEBUG(("ASIOGetSampleRate:%f\n",oldRate)); + + if (oldRate != sampleRate){ + /* Set sample rate */ + + PA_DEBUG(("before ASIOSetSampleRate(%f)\n",sampleRate)); + + /* + If you have problems with some drivers when externally clocked, + try switching on the following line and commenting out the one after it. + See IsUsingExternalClockSource() for more info. + */ + //if( IsUsingExternalClockSource() ){ + if( false ){ + asioError = ASIOSetSampleRate( 0 ); + }else{ + asioError = ASIOSetSampleRate( sampleRate ); + } + if( asioError != ASE_OK ) + { + result = paInvalidSampleRate; + PA_DEBUG(("ERROR: ASIOSetSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); + goto error; + } + PA_DEBUG(("after ASIOSetSampleRate(%f)\n",sampleRate)); + } + else + { + PA_DEBUG(("No Need to change SR\n")); + } + +error: + return result; } @@ -1678,23 +1877,38 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaAsioDriverInfo *driverInfo; int *inputChannelSelectors = 0; int *outputChannelSelectors = 0; - bool isExternal = false; + + /* Are we using blocking i/o interface? */ + int usingBlockingIo = ( !streamCallback ) ? TRUE : FALSE; + /* Blocking i/o stuff */ + long lBlockingBufferSize = 0; /* Desired ring buffer size in samples. */ + long lBlockingBufferSizePow2 = 0; /* Power-of-2 rounded ring buffer size. */ + long lBytesPerFrame = 0; /* Number of bytes per input/output frame. */ + int blockingWriteBuffersReadyEventInitialized = 0; /* Event init flag. */ + int blockingReadFramesReadyEventInitialized = 0; /* Event init flag. */ + + int callbackBufferProcessorInited = FALSE; + int blockingBufferProcessorInited = FALSE; /* unless we move to using lower level ASIO calls, we can only have one device open at a time */ - if( asioHostApi->openAsioDeviceIndex != paNoDevice ){ + if( asioHostApi->openAsioDeviceIndex != paNoDevice ) + { PA_DEBUG(("OpenStream paDeviceUnavailable\n")); return paDeviceUnavailable; } + assert( theAsioStream == 0 ); + if( inputParameters && outputParameters ) { /* full duplex ASIO stream must use the same device for input and output */ - if( inputParameters->device != outputParameters->device ){ + if( inputParameters->device != outputParameters->device ) + { PA_DEBUG(("OpenStream paBadIODeviceCombination\n")); return paBadIODeviceCombination; - } + } } if( inputParameters ) @@ -1762,12 +1976,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, /* NOTE: we load the driver and use its current settings rather than the ones in our device info structure which may be stale */ - result = LoadAsioDriver( asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, + result = LoadAsioDriver( asioHostApi, asioHostApi->inheritedHostApiRep.deviceInfos[ asioDeviceIndex ]->name, driverInfo, asioHostApi->systemSpecific ); if( result == paNoError ) asioIsInitialized = 1; else{ - PA_DEBUG(("OpenStream ERROR1\n")); + PA_DEBUG(("OpenStream ERROR1 - LoadAsioDriver returned %d\n", result)); goto error; } @@ -1793,76 +2007,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } } - - /* davidv: listing ASIO Clock sources, there is an ongoing investigation by - me about whether or not call ASIOSetSampleRate if an external Clock is - used. A few drivers expected different things here */ - { - ASIOClockSource clocks[32]; - long numSources=32; - asioError = ASIOGetClockSources(clocks, &numSources); - if( asioError != ASE_OK ){ - PA_DEBUG(("ERROR: ASIOGetClockSources: %s\n", PaAsio_GetAsioErrorText(asioError) )); - }else{ - PA_DEBUG(("INFO ASIOGetClockSources listing %d clocks\n", numSources )); - for (int i=0;i<numSources;++i){ - PA_DEBUG(("ASIOClockSource%d %s current:%d\n", i,clocks[i].name, clocks[i].isCurrentSource )); - - /* - If you have problems with some drivers when externally clocked, - uncomment the next two lines - */ - //if (clocks[i].isCurrentSource) - // isExternal = true; - } - } - } - - // check that the device supports the requested sample rate - - asioError = ASIOCanSampleRate( sampleRate ); - PA_DEBUG(("ASIOCanSampleRate(%f):%d\n",sampleRate, asioError )); - - if( asioError != ASE_OK ) - { - result = paInvalidSampleRate; - PA_DEBUG(("ERROR: ASIOCanSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); - goto error; - } - - - // retrieve the current sample rate, we only change to the requested - // sample rate if the device is not already in that rate. - - ASIOSampleRate oldRate; - asioError = ASIOGetSampleRate(&oldRate); - if( asioError != ASE_OK ) - { - result = paInvalidSampleRate; - PA_DEBUG(("ERROR: ASIOGetSampleRate: %s\n", PaAsio_GetAsioErrorText(asioError) )); + result = ValidateAndSetSampleRate( sampleRate ); + if( result != paNoError ) goto error; - } - PA_DEBUG(("ASIOGetSampleRate:%f\n",oldRate)); - - if (oldRate != sampleRate){ - - PA_DEBUG(("before ASIOSetSampleRate(%f)\n",sampleRate)); - - asioError = ASIOSetSampleRate( isExternal?0: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)); - } - else - { - PA_DEBUG(("No Need to change SR\n")); - } - /* IMPLEMENT ME: @@ -1884,6 +2031,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PA_DEBUG(("OpenStream ERROR5\n")); goto error; } + stream->blockingState = NULL; /* Blocking i/o not initialized, yet. */ + stream->completedBuffersPlayedEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); if( stream->completedBuffersPlayedEvent == NULL ) @@ -1900,15 +2049,19 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->asioChannelInfos = 0; /* for deallocation in error */ stream->bufferPtrs = 0; /* for deallocation in error */ - if( streamCallback ) + /* Using blocking i/o interface... */ + if( usingBlockingIo ) { + /* Blocking i/o is implemented by running callback mode, using a special blocking i/o callback. */ + streamCallback = BlockingIoPaCallback; /* Setup PA to use the ASIO blocking i/o callback. */ + userData = &theAsioStream; /* The callback user data will be the PA ASIO stream. */ PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - &asioHostApi->callbackStreamInterface, streamCallback, userData ); + &asioHostApi->blockingStreamInterface, streamCallback, userData ); } - else + else /* Using callback interface... */ { PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, - &asioHostApi->blockingStreamInterface, streamCallback, userData ); + &asioHostApi->callbackStreamInterface, streamCallback, userData ); } @@ -1959,13 +2112,24 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } - framesPerHostBuffer = SelectHostBufferSize( - (( suggestedInputLatencyFrames > suggestedOutputLatencyFrames ) - ? suggestedInputLatencyFrames : suggestedOutputLatencyFrames), - driverInfo ); + /* Using blocking i/o interface... */ + if( usingBlockingIo ) + { +/** @todo REVIEW selection of host buffer size for blocking i/o */ + /* Use default host latency for blocking i/o. */ + framesPerHostBuffer = SelectHostBufferSize( 0, driverInfo ); + + } + else /* Using callback interface... */ + { + framesPerHostBuffer = SelectHostBufferSize( + (( suggestedInputLatencyFrames > suggestedOutputLatencyFrames ) + ? suggestedInputLatencyFrames : suggestedOutputLatencyFrames), + driverInfo ); + } - PA_DEBUG(("PaAsioOpenStream: framesPerHostBuffer :%d\n", framesPerHostBuffer)); + PA_DEBUG(("PaAsioOpenStream: framesPerHostBuffer :%d\n", framesPerHostBuffer)); asioError = ASIOCreateBuffers( stream->asioBufferInfos, inputChannelCount+outputChannelCount, @@ -2103,43 +2267,302 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->outputBufferConverter = 0; } - result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, - inputChannelCount, inputSampleFormat, hostInputSampleFormat, - outputChannelCount, outputSampleFormat, hostOutputSampleFormat, - sampleRate, streamFlags, framesPerBuffer, - framesPerHostBuffer, paUtilFixedHostBufferSize, - streamCallback, userData ); - if( result != paNoError ){ - PA_DEBUG(("OpenStream ERROR13\n")); - goto error; - } - ASIOGetLatencies( &stream->inputLatency, &stream->outputLatency ); - stream->streamRepresentation.streamInfo.inputLatency = - (double)( PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) - + stream->inputLatency) / sampleRate; // seconds - stream->streamRepresentation.streamInfo.outputLatency = - (double)( PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) - + stream->outputLatency) / sampleRate; // seconds - stream->streamRepresentation.streamInfo.sampleRate = sampleRate; - - // the code below prints the ASIO latency which doesn't include the - // buffer processor latency. it reports the added latency separately - PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", - stream->inputLatency, - (long)((stream->inputLatency*1000)/ sampleRate), - PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor), - (long)((PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)*1000)/ sampleRate) - )); - - PA_DEBUG(("PaAsio : ASIO OuputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", - stream->outputLatency, - (long)((stream->outputLatency*1000)/ sampleRate), - PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor), - (long)((PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)*1000)/ sampleRate) - )); + + /* Using blocking i/o interface... */ + if( usingBlockingIo ) + { + /* Allocate the blocking i/o input ring buffer memory. */ + stream->blockingState = (PaAsioStreamBlockingState*)PaUtil_AllocateMemory( sizeof(PaAsioStreamBlockingState) ); + if( !stream->blockingState ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o interface struct allocation failed in OpenStream()\n")); + goto error; + } + + /* Initialize blocking i/o interface struct. */ + stream->blockingState->readFramesReadyEvent = NULL; /* Uninitialized, yet. */ + stream->blockingState->writeBuffersReadyEvent = NULL; /* Uninitialized, yet. */ + stream->blockingState->readRingBufferData = NULL; /* Uninitialized, yet. */ + stream->blockingState->writeRingBufferData = NULL; /* Uninitialized, yet. */ + stream->blockingState->readStreamBuffer = NULL; /* Uninitialized, yet. */ + stream->blockingState->writeStreamBuffer = NULL; /* Uninitialized, yet. */ + stream->blockingState->stopFlag = TRUE; /* Not started, yet. */ + + + /* If the user buffer is unspecified */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + /* Make the user buffer the same size as the host buffer. */ + framesPerBuffer = framesPerHostBuffer; + } + + + /* Initialize callback buffer processor. */ + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor , + inputChannelCount , + inputSampleFormat & ~paNonInterleaved , /* Ring buffer. */ + hostInputSampleFormat , /* Host format. */ + outputChannelCount , + outputSampleFormat & ~paNonInterleaved, /* Ring buffer. */ + hostOutputSampleFormat , /* Host format. */ + sampleRate , + streamFlags , + framesPerBuffer , /* Frames per ring buffer block. */ + framesPerHostBuffer , /* Frames per asio buffer. */ + paUtilFixedHostBufferSize , + streamCallback , + userData ); + if( result != paNoError ){ + PA_DEBUG(("OpenStream ERROR13\n")); + goto error; + } + callbackBufferProcessorInited = TRUE; + + /* Initialize the blocking i/o buffer processor. */ + result = PaUtil_InitializeBufferProcessor(&stream->blockingState->bufferProcessor, + inputChannelCount , + inputSampleFormat , /* User format. */ + inputSampleFormat & ~paNonInterleaved , /* Ring buffer. */ + outputChannelCount , + outputSampleFormat , /* User format. */ + outputSampleFormat & ~paNonInterleaved, /* Ring buffer. */ + sampleRate , + paClipOff | paDitherOff , /* Don't use dither nor clipping. */ + framesPerBuffer , /* Frames per user buffer. */ + framesPerBuffer , /* Frames per ring buffer block. */ + paUtilBoundedHostBufferSize , + NULL, NULL );/* No callback! */ + if( result != paNoError ){ + PA_DEBUG(("ERROR! Blocking i/o buffer processor initialization failed in OpenStream()\n")); + goto error; + } + blockingBufferProcessorInited = TRUE; + + /* If input is requested. */ + if( inputChannelCount ) + { + /* Create the callback sync-event. */ + stream->blockingState->readFramesReadyEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( stream->blockingState->readFramesReadyEvent == NULL ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + PA_DEBUG(("ERROR! Blocking i/o \"read frames ready\" event creation failed in OpenStream()\n")); + goto error; + } + blockingReadFramesReadyEventInitialized = 1; + + + /* Create pointer buffer to access non-interleaved data in ReadStream() */ + stream->blockingState->readStreamBuffer = (void**)PaUtil_AllocateMemory( sizeof(void*) * inputChannelCount ); + if( !stream->blockingState->readStreamBuffer ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o read stream buffer allocation failed in OpenStream()\n")); + goto error; + } + + /* The ring buffer should store as many data blocks as needed + to achieve the requested latency. Whereas it must be large + enough to store at least two complete data blocks. + + 1) Determine the amount of latency to be added to the + prefered ASIO latency. + 2) Make sure we have at lest one additional latency frame. + 3) Divide the number of frames by the desired block size to + get the number (rounded up to pure integer) of blocks to + be stored in the buffer. + 4) Add one additional block for block processing and convert + to samples frames. + 5) Get the next larger (or equal) power-of-two buffer size. + */ + lBlockingBufferSize = suggestedInputLatencyFrames - stream->inputLatency; + lBlockingBufferSize = (lBlockingBufferSize > 0) ? lBlockingBufferSize : 1; + lBlockingBufferSize = (lBlockingBufferSize + framesPerBuffer - 1) / framesPerBuffer; + lBlockingBufferSize = (lBlockingBufferSize + 1) * framesPerBuffer; + + /* Get the next larger or equal power-of-two buffersize. */ + lBlockingBufferSizePow2 = 1; + while( lBlockingBufferSize > (lBlockingBufferSizePow2<<=1) ); + lBlockingBufferSize = lBlockingBufferSizePow2; + + /* Compute total intput latency in seconds */ + stream->streamRepresentation.streamInfo.inputLatency = + (double)( PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor ) + + PaUtil_GetBufferProcessorInputLatency(&stream->blockingState->bufferProcessor) + + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer + + stream->inputLatency ) + / sampleRate; + + /* The code below prints the ASIO latency which doesn't include + the buffer processor latency nor the blocking i/o latency. It + reports the added latency separately. + */ + PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms),\n added buffProc:%ld (%ld ms),\n added blocking:%ld (%ld ms)\n", + stream->inputLatency, + (long)( stream->inputLatency * (1000.0 / sampleRate) ), + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor), + (long)( PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) * (1000.0 / sampleRate) ), + PaUtil_GetBufferProcessorInputLatency(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer, + (long)( (PaUtil_GetBufferProcessorInputLatency(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer) * (1000.0 / sampleRate) ) + )); + + /* Determine the size of ring buffer in bytes. */ + lBytesPerFrame = inputChannelCount * Pa_GetSampleSize(inputSampleFormat ); + + /* Allocate the blocking i/o input ring buffer memory. */ + stream->blockingState->readRingBufferData = (void*)PaUtil_AllocateMemory( lBlockingBufferSize * lBytesPerFrame ); + if( !stream->blockingState->readRingBufferData ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o input ring buffer allocation failed in OpenStream()\n")); + goto error; + } + + /* Initialize the input ring buffer struct. */ + PaUtil_InitializeRingBuffer( &stream->blockingState->readRingBuffer , + lBytesPerFrame , + lBlockingBufferSize , + stream->blockingState->readRingBufferData ); + } + + /* If output is requested. */ + if( outputChannelCount ) + { + stream->blockingState->writeBuffersReadyEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if( stream->blockingState->writeBuffersReadyEvent == NULL ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + PA_DEBUG(("ERROR! Blocking i/o \"write buffers ready\" event creation failed in OpenStream()\n")); + goto error; + } + blockingWriteBuffersReadyEventInitialized = 1; + + /* Create pointer buffer to access non-interleaved data in WriteStream() */ + stream->blockingState->writeStreamBuffer = (const void**)PaUtil_AllocateMemory( sizeof(const void*) * outputChannelCount ); + if( !stream->blockingState->writeStreamBuffer ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o write stream buffer allocation failed in OpenStream()\n")); + goto error; + } + + /* The ring buffer should store as many data blocks as needed + to achieve the requested latency. Whereas it must be large + enough to store at least two complete data blocks. + + 1) Determine the amount of latency to be added to the + prefered ASIO latency. + 2) Make sure we have at lest one additional latency frame. + 3) Divide the number of frames by the desired block size to + get the number (rounded up to pure integer) of blocks to + be stored in the buffer. + 4) Add one additional block for block processing and convert + to samples frames. + 5) Get the next larger (or equal) power-of-two buffer size. + */ + lBlockingBufferSize = suggestedOutputLatencyFrames - stream->outputLatency; + lBlockingBufferSize = (lBlockingBufferSize > 0) ? lBlockingBufferSize : 1; + lBlockingBufferSize = (lBlockingBufferSize + framesPerBuffer - 1) / framesPerBuffer; + lBlockingBufferSize = (lBlockingBufferSize + 1) * framesPerBuffer; + + /* The buffer size (without the additional block) corresponds + to the initial number of silent samples in the output ring + buffer. */ + stream->blockingState->writeRingBufferInitialFrames = lBlockingBufferSize - framesPerBuffer; + + /* Get the next larger or equal power-of-two buffersize. */ + lBlockingBufferSizePow2 = 1; + while( lBlockingBufferSize > (lBlockingBufferSizePow2<<=1) ); + lBlockingBufferSize = lBlockingBufferSizePow2; + + /* Compute total output latency in seconds */ + stream->streamRepresentation.streamInfo.outputLatency = + (double)( PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor ) + + PaUtil_GetBufferProcessorOutputLatency(&stream->blockingState->bufferProcessor) + + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer + + stream->outputLatency ) + / sampleRate; + + /* The code below prints the ASIO latency which doesn't include + the buffer processor latency nor the blocking i/o latency. It + reports the added latency separately. + */ + PA_DEBUG(("PaAsio : ASIO OutputLatency = %ld (%ld ms),\n added buffProc:%ld (%ld ms),\n added blocking:%ld (%ld ms)\n", + stream->outputLatency, + (long)( stream->inputLatency * (1000.0 / sampleRate) ), + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor), + (long)( PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) * (1000.0 / sampleRate) ), + PaUtil_GetBufferProcessorOutputLatency(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer, + (long)( (PaUtil_GetBufferProcessorOutputLatency(&stream->blockingState->bufferProcessor) + (lBlockingBufferSize / framesPerBuffer - 1) * framesPerBuffer) * (1000.0 / sampleRate) ) + )); + + /* Determine the size of ring buffer in bytes. */ + lBytesPerFrame = outputChannelCount * Pa_GetSampleSize(outputSampleFormat); + + /* Allocate the blocking i/o output ring buffer memory. */ + stream->blockingState->writeRingBufferData = (void*)PaUtil_AllocateMemory( lBlockingBufferSize * lBytesPerFrame ); + if( !stream->blockingState->writeRingBufferData ) + { + result = paInsufficientMemory; + PA_DEBUG(("ERROR! Blocking i/o output ring buffer allocation failed in OpenStream()\n")); + goto error; + } + + /* Initialize the output ring buffer struct. */ + PaUtil_InitializeRingBuffer( &stream->blockingState->writeRingBuffer , + lBytesPerFrame , + lBlockingBufferSize , + stream->blockingState->writeRingBufferData ); + } + + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + + } + else /* Using callback interface... */ + { + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerHostBuffer, paUtilFixedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ){ + PA_DEBUG(("OpenStream ERROR13\n")); + goto error; + } + callbackBufferProcessorInited = TRUE; + + stream->streamRepresentation.streamInfo.inputLatency = + (double)( PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor) + + stream->inputLatency) / sampleRate; // seconds + stream->streamRepresentation.streamInfo.outputLatency = + (double)( PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor) + + stream->outputLatency) / sampleRate; // seconds + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + // the code below prints the ASIO latency which doesn't include the + // buffer processor latency. it reports the added latency separately + PA_DEBUG(("PaAsio : ASIO InputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", + stream->inputLatency, + (long)((stream->inputLatency*1000)/ sampleRate), + PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor), + (long)((PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)*1000)/ sampleRate) + )); + + PA_DEBUG(("PaAsio : ASIO OuputLatency = %ld (%ld ms), added buffProc:%ld (%ld ms)\n", + stream->outputLatency, + (long)((stream->outputLatency*1000)/ sampleRate), + PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor), + (long)((PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)*1000)/ sampleRate) + )); + } stream->asioHostApi = asioHostApi; stream->framesPerHostCallback = framesPerHostBuffer; @@ -2147,10 +2570,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->inputChannelCount = inputChannelCount; stream->outputChannelCount = outputChannelCount; stream->postOutput = driverInfo->postOutput; + stream->isStopped = 1; stream->isActive = 0; - + asioHostApi->openAsioDeviceIndex = asioDeviceIndex; + theAsioStream = stream; *s = (PaStream*)stream; return result; @@ -2159,6 +2584,31 @@ error: PA_DEBUG(("goto errored\n")); if( stream ) { + if( stream->blockingState ) + { + if( blockingBufferProcessorInited ) + PaUtil_TerminateBufferProcessor( &stream->blockingState->bufferProcessor ); + + if( stream->blockingState->writeRingBufferData ) + PaUtil_FreeMemory( stream->blockingState->writeRingBufferData ); + if( stream->blockingState->writeStreamBuffer ) + PaUtil_FreeMemory( stream->blockingState->writeStreamBuffer ); + if( blockingWriteBuffersReadyEventInitialized ) + CloseHandle( stream->blockingState->writeBuffersReadyEvent ); + + if( stream->blockingState->readRingBufferData ) + PaUtil_FreeMemory( stream->blockingState->readRingBufferData ); + if( stream->blockingState->readStreamBuffer ) + PaUtil_FreeMemory( stream->blockingState->readStreamBuffer ); + if( blockingReadFramesReadyEventInitialized ) + CloseHandle( stream->blockingState->readFramesReadyEvent ); + + PaUtil_FreeMemory( stream->blockingState ); + } + + if( callbackBufferProcessorInited ) + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + if( completedBuffersPlayedEventInited ) CloseHandle( stream->completedBuffersPlayedEvent ); @@ -2178,8 +2628,9 @@ error: ASIODisposeBuffers(); if( asioIsInitialized ) - ASIOExit(); - + { + UnloadAsioDriver(); + } return result; } @@ -2205,13 +2656,34 @@ static PaError CloseStream( PaStream* s ) CloseHandle( stream->completedBuffersPlayedEvent ); + /* Using blocking i/o interface... */ + if( stream->blockingState ) + { + PaUtil_TerminateBufferProcessor( &stream->blockingState->bufferProcessor ); + + if( stream->inputChannelCount ) { + PaUtil_FreeMemory( stream->blockingState->readRingBufferData ); + PaUtil_FreeMemory( stream->blockingState->readStreamBuffer ); + CloseHandle( stream->blockingState->readFramesReadyEvent ); + } + if( stream->outputChannelCount ) { + PaUtil_FreeMemory( stream->blockingState->writeRingBufferData ); + PaUtil_FreeMemory( stream->blockingState->writeStreamBuffer ); + CloseHandle( stream->blockingState->writeBuffersReadyEvent ); + } + + PaUtil_FreeMemory( stream->blockingState ); + } + PaUtil_FreeMemory( stream->asioBufferInfos ); PaUtil_FreeMemory( stream->asioChannelInfos ); PaUtil_FreeMemory( stream->bufferPtrs ); PaUtil_FreeMemory( stream ); ASIODisposeBuffers(); - ASIOExit(); + UnloadAsioDriver(); + + theAsioStream = 0; return result; } @@ -2245,10 +2717,10 @@ static void bufferSwitch(long index, ASIOBool directProcess) // conversion from 64 bit ASIOSample/ASIOTimeStamp to double float #if NATIVE_INT64 - #define ASIO64toDouble(a) (a) + #define ASIO64toDouble(a) (a) #else - const double twoRaisedTo32 = 4294967296.; - #define ASIO64toDouble(a) ((a).lo + (a).hi * twoRaisedTo32) + const double twoRaisedTo32 = 4294967296.; + #define ASIO64toDouble(a) ((a).lo + (a).hi * twoRaisedTo32) #endif static ASIOTime *bufferSwitchTimeInfo( ASIOTime *timeInfo, long index, ASIOBool directProcess ) @@ -2430,7 +2902,9 @@ previousIndex = index; paTimeInfo.inputBufferAdcTime = paTimeInfo.currentTime - theAsioStream->streamRepresentation.streamInfo.inputLatency; paTimeInfo.outputBufferDacTime = paTimeInfo.currentTime + theAsioStream->streamRepresentation.streamInfo.outputLatency; */ -#if 1 + +/* Disabled! Stopping and re-starting the stream causes an input overflow / output undeflow. S.Fischer */ +#if 0 // detect underflows by checking inter-callback time > 2 buffer period static double previousTime = -1; if( previousTime > 0 ){ @@ -2634,6 +3108,7 @@ static PaError StartStream( PaStream *s ) { PaError result = paNoError; PaAsioStream *stream = (PaAsioStream*)s; + PaAsioStreamBlockingState *blockingState = stream->blockingState; ASIOError asioError; if( stream->outputChannelCount > 0 ) @@ -2658,18 +3133,72 @@ static PaError StartStream( PaStream *s ) PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); } - if( result == paNoError ) + + /* Using blocking i/o interface... */ + if( blockingState ) { - theAsioStream = stream; - asioError = ASIOStart(); - if( asioError == ASE_OK ) + /* Reset blocking i/o buffer processor. */ + PaUtil_ResetBufferProcessor( &blockingState->bufferProcessor ); + + /* If we're about to process some input data. */ + if( stream->inputChannelCount ) { - stream->isActive = 1; - stream->streamFinishedCallbackCalled = false; + /* Reset callback-ReadStream sync event. */ + if( ResetEvent( blockingState->readFramesReadyEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + + /* Flush blocking i/o ring buffer. */ + PaUtil_FlushRingBuffer( &blockingState->readRingBuffer ); + (*blockingState->bufferProcessor.inputZeroer)( blockingState->readRingBuffer.buffer, 1, blockingState->bufferProcessor.inputChannelCount * blockingState->readRingBuffer.bufferSize ); } - else + + /* If we're about to process some output data. */ + if( stream->outputChannelCount ) + { + /* Reset callback-WriteStream sync event. */ + if( ResetEvent( blockingState->writeBuffersReadyEvent ) == 0 ) + { + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + + /* Flush blocking i/o ring buffer. */ + PaUtil_FlushRingBuffer( &blockingState->writeRingBuffer ); + (*blockingState->bufferProcessor.outputZeroer)( blockingState->writeRingBuffer.buffer, 1, blockingState->bufferProcessor.outputChannelCount * blockingState->writeRingBuffer.bufferSize ); + + /* Initialize the output ring buffer to "silence". */ + PaUtil_AdvanceRingBufferWriteIndex( &blockingState->writeRingBuffer, blockingState->writeRingBufferInitialFrames ); + } + + /* Clear requested frames / buffers count. */ + blockingState->writeBuffersRequested = 0; + blockingState->readFramesRequested = 0; + blockingState->writeBuffersRequestedFlag = FALSE; + blockingState->readFramesRequestedFlag = FALSE; + blockingState->outputUnderflowFlag = FALSE; + blockingState->inputOverflowFlag = FALSE; + blockingState->stopFlag = FALSE; + } + + + if( result == paNoError ) + { + assert( theAsioStream == stream ); /* theAsioStream should be set correctly in OpenStream */ + + /* initialize these variables before the callback has a chance to be invoked */ + stream->isStopped = 0; + stream->isActive = 1; + stream->streamFinishedCallbackCalled = false; + + asioError = ASIOStart(); + if( asioError != ASE_OK ) { - theAsioStream = 0; + stream->isStopped = 1; + stream->isActive = 0; + result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); } @@ -2678,15 +3207,59 @@ static PaError StartStream( PaStream *s ) return result; } +static void EnsureCallbackHasCompleted( PaAsioStream *stream ) +{ + // make sure that the callback is not still in-flight after ASIOStop() + // returns. This has been observed to happen on the Hoontech DSP24 for + // example. + int count = 2000; // only wait for 2 seconds, rather than hanging. + while( stream->reenterCount != -1 && count > 0 ) + { + Sleep(1); + --count; + } +} static PaError StopStream( PaStream *s ) { PaError result = paNoError; PaAsioStream *stream = (PaAsioStream*)s; + PaAsioStreamBlockingState *blockingState = stream->blockingState; ASIOError asioError; if( stream->isActive ) { + /* If blocking i/o output is in use */ + if( blockingState && stream->outputChannelCount ) + { + /* Request the whole output buffer to be available. */ + blockingState->writeBuffersRequested = blockingState->writeRingBuffer.bufferSize; + /* Signalize that additional buffers are need. */ + blockingState->writeBuffersRequestedFlag = TRUE; + /* Set flag to indicate the playback is to be stopped. */ + blockingState->stopFlag = TRUE; + + /* Wait until requested number of buffers has been freed. Time + out after twice the blocking i/o ouput buffer could have + been consumed. */ + DWORD timeout = (DWORD)( 2 * blockingState->writeRingBuffer.bufferSize * 1000 + / stream->streamRepresentation.streamInfo.sampleRate ); + DWORD waitResult = WaitForSingleObject( blockingState->writeBuffersReadyEvent, timeout ); + + /* If something seriously went wrong... */ + if( waitResult == WAIT_FAILED ) + { + PA_DEBUG(("WaitForSingleObject() failed in StopStream()\n")); + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + } + else if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WaitForSingleObject() timed out in StopStream()\n")); + result = paTimedOut; + } + } + stream->stopProcessing = true; /* wait for the stream to finish playing out enqueued buffers. @@ -2696,22 +3269,26 @@ static PaError StopStream( PaStream *s ) length is longer than the asio buffer size then that should be taken into account. */ - if( WaitForSingleObject( theAsioStream->completedBuffersPlayedEvent, + if( WaitForSingleObject( stream->completedBuffersPlayedEvent, (DWORD)(stream->streamRepresentation.streamInfo.outputLatency * 1000. * 4.) ) - == WAIT_TIMEOUT ) + == WAIT_TIMEOUT ) { PA_DEBUG(("WaitForSingleObject() timed out in StopStream()\n" )); } } asioError = ASIOStop(); - if( asioError != ASE_OK ) + if( asioError == ASE_OK ) + { + EnsureCallbackHasCompleted( stream ); + } + else { result = paUnanticipatedHostError; PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); } - theAsioStream = 0; + stream->isStopped = 1; stream->isActive = 0; if( !stream->streamFinishedCallbackCalled ) @@ -2723,7 +3300,6 @@ static PaError StopStream( PaStream *s ) return result; } - static PaError AbortStream( PaStream *s ) { PaError result = paNoError; @@ -2733,31 +3309,17 @@ static PaError AbortStream( PaStream *s ) stream->zeroOutput = true; asioError = ASIOStop(); - if( asioError != ASE_OK ) + if( asioError == ASE_OK ) { - result = paUnanticipatedHostError; - PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); + EnsureCallbackHasCompleted( stream ); } else { - // make sure that the callback is not still in-flight when ASIOStop() - // returns. This has been observed to happen on the Hoontech DSP24 for - // example. - int count = 2000; // only wait for 2 seconds, rather than hanging. - while( theAsioStream->reenterCount != -1 && count > 0 ) - { - Sleep(1); - --count; - } + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_ASIO_ERROR( asioError ); } - /* it is questionable whether we should zero theAsioStream if ASIOStop() - returns an error, because the callback could still be active. We assume - not - this is based on the fact that ASIOStop is unlikely to fail - if the callback is running - it's more likely to fail because the - callback is not running. */ - - theAsioStream = 0; + stream->isStopped = 1; stream->isActive = 0; if( !stream->streamFinishedCallbackCalled ) @@ -2772,9 +3334,9 @@ static PaError AbortStream( PaStream *s ) static PaError IsStreamStopped( PaStream *s ) { - //PaAsioStream *stream = (PaAsioStream*)s; - (void) s; /* unused parameter */ - return theAsioStream == 0; + PaAsioStream *stream = (PaAsioStream*)s; + + return stream->isStopped; } @@ -2807,33 +3369,348 @@ static double GetStreamCpuLoad( PaStream* s ) for blocking streams. */ -static PaError ReadStream( PaStream* s, - void *buffer, - unsigned long frames ) +static PaError ReadStream( PaStream *s , + void *buffer, + unsigned long frames ) { - PaAsioStream *stream = (PaAsioStream*)s; + PaError result = paNoError; /* Initial return value. */ + PaAsioStream *stream = (PaAsioStream*)s; /* The PA ASIO stream. */ - /* IMPLEMENT ME, see portaudio.h for required behavior*/ - (void) stream; /* unused parameters */ - (void) buffer; - (void) frames; + /* Pointer to the blocking i/o data struct. */ + PaAsioStreamBlockingState *blockingState = stream->blockingState; - return paNoError; -} + /* Get blocking i/o buffer processor and ring buffer pointers. */ + PaUtilBufferProcessor *pBp = &blockingState->bufferProcessor; + PaUtilRingBuffer *pRb = &blockingState->readRingBuffer; + + /* Ring buffer segment(s) used for writing. */ + void *pRingBufferData1st = NULL; /* First segment. (Mandatory) */ + void *pRingBufferData2nd = NULL; /* Second segment. (Optional) */ + + /* Number of frames per ring buffer segment. */ + long lRingBufferSize1st = 0; /* First segment. (Mandatory) */ + long lRingBufferSize2nd = 0; /* Second segment. (Optional) */ + + /* Get number of frames to be processed per data block. */ + unsigned long lFramesPerBlock = stream->bufferProcessor.framesPerUserBuffer; + /* Actual number of frames that has been copied into the ring buffer. */ + unsigned long lFramesCopied = 0; + /* The number of remaining unprocessed dtat frames. */ + unsigned long lFramesRemaining = frames; + /* Copy the input argument to avoid pointer increment! */ + const void *userBuffer; + unsigned int i; /* Just a counter. */ + + /* About the time, needed to process 8 data blocks. */ + DWORD timeout = (DWORD)( 8 * lFramesPerBlock * 1000 / stream->streamRepresentation.streamInfo.sampleRate ); + DWORD waitResult = 0; + + + /* Check if the stream is still available ready to gather new data. */ + if( blockingState->stopFlag || !stream->isActive ) + { + PA_DEBUG(("Warning! Stream no longer available for reading in ReadStream()\n")); + result = paStreamIsStopped; + return result; + } + + /* If the stream is a input stream. */ + if( stream->inputChannelCount ) + { + /* Prepare buffer access. */ + if( !pBp->userOutputIsInterleaved ) + { + userBuffer = blockingState->readStreamBuffer; + for( i = 0; i<pBp->inputChannelCount; ++i ) + { + ((void**)userBuffer)[i] = ((void**)buffer)[i]; + } + } /* Use the unchanged buffer. */ + else { userBuffer = buffer; } -static PaError WriteStream( PaStream* s, - const void *buffer, - unsigned long frames ) + do /* Internal block processing for too large user data buffers. */ + { + /* Get the size of the current data block to be processed. */ + lFramesPerBlock =(lFramesPerBlock < lFramesRemaining) + ? lFramesPerBlock : lFramesRemaining; + /* Use predefined block size for as long there are enough + buffers available, thereafter reduce the processing block + size to match the number of remaining buffers. So the final + data block is processed although it may be incomplete. */ + + /* If the available amount of data frames is insufficient. */ + if( PaUtil_GetRingBufferReadAvailable(pRb) < (long) lFramesPerBlock ) + { + /* Make sure, the event isn't already set! */ + /* ResetEvent( blockingState->readFramesReadyEvent ); */ + + /* Set the number of requested buffers. */ + blockingState->readFramesRequested = lFramesPerBlock; + + /* Signalize that additional buffers are need. */ + blockingState->readFramesRequestedFlag = TRUE; + + /* Wait until requested number of buffers has been freed. */ + waitResult = WaitForSingleObject( blockingState->readFramesReadyEvent, timeout ); + + /* If something seriously went wrong... */ + if( waitResult == WAIT_FAILED ) + { + PA_DEBUG(("WaitForSingleObject() failed in ReadStream()\n")); + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + return result; + } + else if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WaitForSingleObject() timed out in ReadStream()\n")); + + /* If block processing has stopped, abort! */ + if( blockingState->stopFlag ) { return result = paStreamIsStopped; } + + /* If a timeout is encountered, give up eventually. */ + return result = paTimedOut; + } + } + /* Now, the ring buffer contains the required amount of data + frames. + (Therefor we don't need to check the return argument of + PaUtil_GetRingBufferReadRegions(). ;-) ) + */ + + /* Retrieve pointer(s) to the ring buffer's current write + position(s). If the first buffer segment is too small to + store the requested number of bytes, an additional second + segment is returned. Otherwise, i.e. if the first segment + is large enough, the second segment's pointer will be NULL. + */ + PaUtil_GetRingBufferReadRegions(pRb , + lFramesPerBlock , + &pRingBufferData1st, + &lRingBufferSize1st, + &pRingBufferData2nd, + &lRingBufferSize2nd); + + /* Set number of frames to be copied from the ring buffer. */ + PaUtil_SetInputFrameCount( pBp, lRingBufferSize1st ); + /* Setup ring buffer access. */ + PaUtil_SetInterleavedInputChannels(pBp , /* Buffer processor. */ + 0 , /* The first channel's index. */ + pRingBufferData1st, /* First ring buffer segment. */ + 0 ); /* Use all available channels. */ + + /* If a second ring buffer segment is required. */ + if( lRingBufferSize2nd ) { + /* Set number of frames to be copied from the ring buffer. */ + PaUtil_Set2ndInputFrameCount( pBp, lRingBufferSize2nd ); + /* Setup ring buffer access. */ + PaUtil_Set2ndInterleavedInputChannels(pBp , /* Buffer processor. */ + 0 , /* The first channel's index. */ + pRingBufferData2nd, /* Second ring buffer segment. */ + 0 ); /* Use all available channels. */ + } + + /* Let the buffer processor handle "copy and conversion" and + update the ring buffer indices manually. */ + lFramesCopied = PaUtil_CopyInput( pBp, &buffer, lFramesPerBlock ); + PaUtil_AdvanceRingBufferReadIndex( pRb, lFramesCopied ); + + /* Decrease number of unprocessed frames. */ + lFramesRemaining -= lFramesCopied; + + } /* Continue with the next data chunk. */ + while( lFramesRemaining ); + + + /* If there has been an input overflow within the callback */ + if( blockingState->inputOverflowFlag ) + { + blockingState->inputOverflowFlag = FALSE; + + /* Return the corresponding error code. */ + result = paInputOverflowed; + } + + } /* If this is not an input stream. */ + else { + result = paCanNotReadFromAnOutputOnlyStream; + } + + return result; +} + +static PaError WriteStream( PaStream *s , + const void *buffer, + unsigned long frames ) { - PaAsioStream *stream = (PaAsioStream*)s; + PaError result = paNoError; /* Initial return value. */ + PaAsioStream *stream = (PaAsioStream*)s; /* The PA ASIO stream. */ - /* IMPLEMENT ME, see portaudio.h for required behavior*/ - (void) stream; /* unused parameters */ - (void) buffer; - (void) frames; + /* Pointer to the blocking i/o data struct. */ + PaAsioStreamBlockingState *blockingState = stream->blockingState; - return paNoError; + /* Get blocking i/o buffer processor and ring buffer pointers. */ + PaUtilBufferProcessor *pBp = &blockingState->bufferProcessor; + PaUtilRingBuffer *pRb = &blockingState->writeRingBuffer; + + /* Ring buffer segment(s) used for writing. */ + void *pRingBufferData1st = NULL; /* First segment. (Mandatory) */ + void *pRingBufferData2nd = NULL; /* Second segment. (Optional) */ + + /* Number of frames per ring buffer segment. */ + long lRingBufferSize1st = 0; /* First segment. (Mandatory) */ + long lRingBufferSize2nd = 0; /* Second segment. (Optional) */ + + /* Get number of frames to be processed per data block. */ + unsigned long lFramesPerBlock = stream->bufferProcessor.framesPerUserBuffer; + /* Actual number of frames that has been copied into the ring buffer. */ + unsigned long lFramesCopied = 0; + /* The number of remaining unprocessed dtat frames. */ + unsigned long lFramesRemaining = frames; + + /* About the time, needed to process 8 data blocks. */ + DWORD timeout = (DWORD)( 8 * lFramesPerBlock * 1000 / stream->streamRepresentation.streamInfo.sampleRate ); + DWORD waitResult = 0; + + /* Copy the input argument to avoid pointer increment! */ + const void *userBuffer; + unsigned int i; /* Just a counter. */ + + + /* Check if the stream ist still available ready to recieve new data. */ + if( blockingState->stopFlag || !stream->isActive ) + { + PA_DEBUG(("Warning! Stream no longer available for writing in WriteStream()\n")); + result = paStreamIsStopped; + return result; + } + + /* If the stream is a output stream. */ + if( stream->outputChannelCount ) + { + /* Prepare buffer access. */ + if( !pBp->userOutputIsInterleaved ) + { + userBuffer = blockingState->writeStreamBuffer; + for( i = 0; i<pBp->outputChannelCount; ++i ) + { + ((const void**)userBuffer)[i] = ((const void**)buffer)[i]; + } + } /* Use the unchanged buffer. */ + else { userBuffer = buffer; } + + + do /* Internal block processing for too large user data buffers. */ + { + /* Get the size of the current data block to be processed. */ + lFramesPerBlock =(lFramesPerBlock < lFramesRemaining) + ? lFramesPerBlock : lFramesRemaining; + /* Use predefined block size for as long there are enough + frames available, thereafter reduce the processing block + size to match the number of remaining frames. So the final + data block is processed although it may be incomplete. */ + + /* If the available amount of buffers is insufficient. */ + if( PaUtil_GetRingBufferWriteAvailable(pRb) < (long) lFramesPerBlock ) + { + /* Make sure, the event isn't already set! */ + /* ResetEvent( blockingState->writeBuffersReadyEvent ); */ + + /* Set the number of requested buffers. */ + blockingState->writeBuffersRequested = lFramesPerBlock; + + /* Signalize that additional buffers are need. */ + blockingState->writeBuffersRequestedFlag = TRUE; + + /* Wait until requested number of buffers has been freed. */ + waitResult = WaitForSingleObject( blockingState->writeBuffersReadyEvent, timeout ); + + /* If something seriously went wrong... */ + if( waitResult == WAIT_FAILED ) + { + PA_DEBUG(("WaitForSingleObject() failed in WriteStream()\n")); + result = paUnanticipatedHostError; + PA_ASIO_SET_LAST_SYSTEM_ERROR( GetLastError() ); + return result; + } + else if( waitResult == WAIT_TIMEOUT ) + { + PA_DEBUG(("WaitForSingleObject() timed out in WriteStream()\n")); + + /* If block processing has stopped, abort! */ + if( blockingState->stopFlag ) { return result = paStreamIsStopped; } + + /* If a timeout is encountered, give up eventually. */ + return result = paTimedOut; + } + } + /* Now, the ring buffer contains the required amount of free + space to store the provided number of data frames. + (Therefor we don't need to check the return argument of + PaUtil_GetRingBufferWriteRegions(). ;-) ) + */ + + /* Retrieve pointer(s) to the ring buffer's current write + position(s). If the first buffer segment is too small to + store the requested number of bytes, an additional second + segment is returned. Otherwise, i.e. if the first segment + is large enough, the second segment's pointer will be NULL. + */ + PaUtil_GetRingBufferWriteRegions(pRb , + lFramesPerBlock , + &pRingBufferData1st, + &lRingBufferSize1st, + &pRingBufferData2nd, + &lRingBufferSize2nd); + + /* Set number of frames to be copied to the ring buffer. */ + PaUtil_SetOutputFrameCount( pBp, lRingBufferSize1st ); + /* Setup ring buffer access. */ + PaUtil_SetInterleavedOutputChannels(pBp , /* Buffer processor. */ + 0 , /* The first channel's index. */ + pRingBufferData1st, /* First ring buffer segment. */ + 0 ); /* Use all available channels. */ + + /* If a second ring buffer segment is required. */ + if( lRingBufferSize2nd ) { + /* Set number of frames to be copied to the ring buffer. */ + PaUtil_Set2ndOutputFrameCount( pBp, lRingBufferSize2nd ); + /* Setup ring buffer access. */ + PaUtil_Set2ndInterleavedOutputChannels(pBp , /* Buffer processor. */ + 0 , /* The first channel's index. */ + pRingBufferData2nd, /* Second ring buffer segment. */ + 0 ); /* Use all available channels. */ + } + + /* Let the buffer processor handle "copy and conversion" and + update the ring buffer indices manually. */ + lFramesCopied = PaUtil_CopyOutput( pBp, &userBuffer, lFramesPerBlock ); + PaUtil_AdvanceRingBufferWriteIndex( pRb, lFramesCopied ); + + /* Decrease number of unprocessed frames. */ + lFramesRemaining -= lFramesCopied; + + } /* Continue with the next data chunk. */ + while( lFramesRemaining ); + + + /* If there has been an output underflow within the callback */ + if( blockingState->outputUnderflowFlag ) + { + blockingState->outputUnderflowFlag = FALSE; + + /* Return the corresponding error code. */ + result = paOutputUnderflowed; + } + + } /* If this is not an output stream. */ + else + { + result = paCanNotWriteToAnInputOnlyStream; + } + + return result; } @@ -2841,10 +3718,8 @@ static signed long GetStreamReadAvailable( PaStream* s ) { PaAsioStream *stream = (PaAsioStream*)s; - /* IMPLEMENT ME, see portaudio.h for required behavior*/ - (void) stream; /* unused parameter */ - - return 0; + /* Call buffer utility routine to get the number of available frames. */ + return PaUtil_GetRingBufferReadAvailable( &stream->blockingState->readRingBuffer ); } @@ -2852,20 +3727,130 @@ static signed long GetStreamWriteAvailable( PaStream* s ) { PaAsioStream *stream = (PaAsioStream*)s; - /* IMPLEMENT ME, see portaudio.h for required behavior*/ - (void) stream; /* unused parameter */ + /* Call buffer utility routine to get the number of empty buffers. */ + return PaUtil_GetRingBufferWriteAvailable( &stream->blockingState->writeRingBuffer ); +} + + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int BlockingIoPaCallback(const void *inputBuffer , + void *outputBuffer , + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo *timeInfo , + PaStreamCallbackFlags statusFlags , + void *userData ) +{ + PaError result = paNoError; /* Initial return value. */ + PaAsioStream *stream = *(PaAsioStream**)userData; /* The PA ASIO stream. */ + PaAsioStreamBlockingState *blockingState = stream->blockingState; /* Persume blockingState is valid, otherwise the callback wouldn't be running. */ + + /* Get a pointer to the stream's blocking i/o buffer processor. */ + PaUtilBufferProcessor *pBp = &blockingState->bufferProcessor; + PaUtilRingBuffer *pRb = NULL; + + /* If output data has been requested. */ + if( stream->outputChannelCount ) + { + /* If the callback input argument signalizes a output underflow, + make sure the WriteStream() function knows about it, too! */ + if( statusFlags & paOutputUnderflowed ) { + blockingState->outputUnderflowFlag = TRUE; + } + + /* Access the corresponding ring buffer. */ + pRb = &blockingState->writeRingBuffer; + + /* If the blocking i/o buffer contains enough output data, */ + if( PaUtil_GetRingBufferReadAvailable(pRb) >= (long) framesPerBuffer ) + { + /* Extract the requested data from the ring buffer. */ + PaUtil_ReadRingBuffer( pRb, outputBuffer, framesPerBuffer ); + } + else /* If no output data is available :-( */ + { + /* Signalize a write-buffer underflow. */ + blockingState->outputUnderflowFlag = TRUE; + + /* Fill the output buffer with silence. */ + (*pBp->outputZeroer)( outputBuffer, 1, pBp->outputChannelCount * framesPerBuffer ); - return 0; + /* If playback is to be stopped */ + if( blockingState->stopFlag && PaUtil_GetRingBufferReadAvailable(pRb) < (long) framesPerBuffer ) + { + /* Extract all the remaining data from the ring buffer, + whether it is a complete data block or not. */ + PaUtil_ReadRingBuffer( pRb, outputBuffer, PaUtil_GetRingBufferReadAvailable(pRb) ); + } + } + + /* Set blocking i/o event? */ + if( blockingState->writeBuffersRequestedFlag && PaUtil_GetRingBufferWriteAvailable(pRb) >= (long) blockingState->writeBuffersRequested ) + { + /* Reset buffer request. */ + blockingState->writeBuffersRequestedFlag = FALSE; + blockingState->writeBuffersRequested = 0; + /* Signalize that requested buffers are ready. */ + SetEvent( blockingState->writeBuffersReadyEvent ); + /* What do we do if SetEvent() returns zero, i.e. the event + could not be set? How to return errors from within the + callback? - S.Fischer */ + } + } + + /* If input data has been supplied. */ + if( stream->inputChannelCount ) + { + /* If the callback input argument signalizes a input overflow, + make sure the ReadStream() function knows about it, too! */ + if( statusFlags & paInputOverflowed ) { + blockingState->inputOverflowFlag = TRUE; + } + + /* Access the corresponding ring buffer. */ + pRb = &blockingState->readRingBuffer; + + /* If the blocking i/o buffer contains not enough input buffers */ + if( PaUtil_GetRingBufferWriteAvailable(pRb) < (long) framesPerBuffer ) + { + /* Signalize a read-buffer overflow. */ + blockingState->inputOverflowFlag = TRUE; + + /* Remove some old data frames from the buffer. */ + PaUtil_AdvanceRingBufferReadIndex( pRb, framesPerBuffer ); + } + + /* Insert the current input data into the ring buffer. */ + PaUtil_WriteRingBuffer( pRb, inputBuffer, framesPerBuffer ); + + /* Set blocking i/o event? */ + if( blockingState->readFramesRequestedFlag && PaUtil_GetRingBufferReadAvailable(pRb) >= (long) blockingState->readFramesRequested ) + { + /* Reset buffer request. */ + blockingState->readFramesRequestedFlag = FALSE; + blockingState->readFramesRequested = 0; + /* Signalize that requested buffers are ready. */ + SetEvent( blockingState->readFramesReadyEvent ); + /* What do we do if SetEvent() returns zero, i.e. the event + could not be set? How to return errors from within the + callback? - S.Fischer */ + /** @todo report an error with PA_DEBUG */ + } + } + + return paContinue; } PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ) { - PaError result = paNoError; + PaError result = paNoError; PaUtilHostApiRepresentation *hostApi; PaDeviceIndex hostApiDevice; ASIODriverInfo asioDriverInfo; - ASIOError asioError; + ASIOError asioError; int asioIsInitialized = 0; PaAsioHostApiRepresentation *asioHostApi; PaAsioDeviceInfo *asioDeviceInfo; @@ -2896,7 +3881,10 @@ PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ) asioDeviceInfo = (PaAsioDeviceInfo*)hostApi->deviceInfos[hostApiDevice]; - if( !loadAsioDriver( const_cast<char*>(asioDeviceInfo->commonDeviceInfo.name) ) ) + /* See notes about CoInitialize(0) in LoadAsioDriver(). */ + CoInitialize(0); + + if( !asioHostApi->asioDrivers->loadDriver( const_cast<char*>(asioDeviceInfo->commonDeviceInfo.name) ) ) { result = paUnanticipatedHostError; goto error; @@ -2944,15 +3932,19 @@ PA_DEBUG(("PaAsio_ShowControlPanel: ASIOControlPanel(): %s\n", PaAsio_GetAsioErr goto error; } + CoUninitialize(); PA_DEBUG(("PaAsio_ShowControlPanel: ASIOExit(): %s\n", PaAsio_GetAsioErrorText(asioError) )); - return result; + return result; error: if( asioIsInitialized ) - ASIOExit(); + { + ASIOExit(); + } + CoUninitialize(); - return result; + return result; } @@ -3021,3 +4013,53 @@ PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, error: return result; } + + +/* NOTE: the following functions are ASIO-stream specific, and are called directly + by client code. We need to check for many more error conditions here because + we don't have the benefit of pa_front.c's parameter checking. +*/ + +static PaError GetAsioStreamPointer( PaAsioStream **stream, PaStream *s ) +{ + PaError result; + PaUtilHostApiRepresentation *hostApi; + PaAsioHostApiRepresentation *asioHostApi; + + result = PaUtil_ValidateStreamPointer( s ); + if( result != paNoError ) + return result; + + result = PaUtil_GetHostApiRepresentation( &hostApi, paASIO ); + if( result != paNoError ) + return result; + + asioHostApi = (PaAsioHostApiRepresentation*)hostApi; + + if( PA_STREAM_REP( s )->streamInterface == &asioHostApi->callbackStreamInterface + || PA_STREAM_REP( s )->streamInterface == &asioHostApi->blockingStreamInterface ) + { + /* s is an ASIO stream */ + *stream = (PaAsioStream *)s; + return paNoError; + } + else + { + return paIncompatibleStreamHostApi; + } +} + + +PaError PaAsio_SetStreamSampleRate( PaStream* s, double sampleRate ) +{ + PaAsioStream *stream; + PaError result = GetAsioStreamPointer( &stream, s ); + if( result != paNoError ) + return result; + + if( stream != theAsioStream ) + return paBadStreamPtr; + + return ValidateAndSetSampleRate( sampleRate ); +} + |