Compare commits

...
Sign in to create a new pull request.
Author SHA1 Message Date
Ross Bencina
45fcad92ed wmme hotplug: General cleanup to device scanning code: * Only check for system preferred devices when there are more than zero devices. * Move variable declarations to more local scope. * Make variable names more meaningful (inputDeviceCount --> waveInDeviceCount, outArgument --> winMmeScanResults) * Add explanatory comments. 2016-09-18 18:15:12 +10:00
Ross Bencina
a9248a2f69 hotplug wmme: clarify confusing device append logic in ScanDeviceInfos(): instead of using newDeviceCount for three different purposes, add new variables: initializedInfosCount (counts the number of currently valid device infos), deviceIndex (index of device whose info is being initialized). 2016-09-18 16:33:08 +10:00
Ross Bencina
a1479a5c9d hotplug wmme: Fix hangs in ReadStream and WriteStream caused by continuous retry with no timeout. The method limits both the number of wait attempts, and the total duration of all waits to 1.5 times the total duration of all buffers. This was reported by Jitsi as part of patch [lyubomir a45b993, Thank you!], although the fix here is different. 2016-09-12 02:12:01 +10:00
Ross Bencina
6a1f1b4057 hotplug wmme: Change StopStream() timeout policy. For callback streams the maximum wait times have been tweaked. Total wait time has been reduced (Old algorithm wait time: [max(2*totalBuffersDur*1.5, 2*1second)], new algorithm wait time: [totalBuffersDur*1.5+min(totalBuffersDur*1.5, 1second)]). For blocking read write streams, the maximum wait time has been reduced to approximately totalBuffersDur*1.5. Added test patest_unplug_readwrite.c for testing stopping read/write streams after unplug. Tweaked patest_unplug.c to build on Windows, and to use 1 input channel (for headset). (Timeout changes made with reference to Jitsi [lyubomir a45b993, Thank you!]. 2016-09-12 01:24:51 +10:00
Ross Bencina
0448f3e7b7 hotplug wmme: only pull default device settings from environment once, at
initialization time. it is designed to operate on the active device list,
so it was a bug to call it from ScanDeviceInfos anyhow.
2016-09-11 23:06:51 +10:00
Ross Bencina
104f656365 hotplug wmme: forgot to initialize PaWinMmeScanDeviceInfosResults fields. this had broken the default devices list 2016-09-11 23:02:46 +10:00
Ross Bencina
def5763094 hotplug wmme: Factor out new Query{Input, Output} DeviceInterfaceName() functions to wrap DRV_QUERYDEVICEINTERFACE. Store the device interface name in PaWinMmeDeviceInfo::deviceInterfaceName, free the associated allocation in FreeDeviceInfos() (required adding deviceCount parameter). Rework WDM/KS-based numChannels query to use the stored deviceInterfaceName. Free name memory on error in InitializeInputDeviceInfo, InitializeOutputDeviceInfo. Implement device connectionId correlation by matching deviceInterfaceName using new functions: FindDeviceByConnectionId(), FindDeviceInfoByInterfaceName(), AssignDeviceConnectionId(). 2016-09-11 20:52:02 +10:00
Ross Bencina
9745b2a590 wmme hotplug: Implement ScanDeviceInfos, CommitDeviceInfos, DisposeDeviceInfos by factoring out the relevant parts from Initialize(). Replace winMmeDeviceIds[] array with storing the native id for the corresponding device's PaWinMmeDeviceInfo struct. Switch to using PA/DirectSound behavior: if enumeration fails on one device, skip it and keep enumerating others. This patch made with reference to Jitsi [5ca6fed damencho], thank you. 2016-09-11 17:25:48 +10:00
Ross Bencina
4072bb5bff wmme: define PAWIN_WMME_NO_WAVE_MAPPER to disable using the WMME WAVE_MAPPER device 2016-09-11 00:49:48 +10:00
Ross Bencina
bc78eab81d wmme: fixed signed/unsigned mismatch warning 2016-09-11 00:26:34 +10:00
Ross Bencina
df5c3f39e6 wmme: fix possible unused variable warning (not a real issue) 2016-09-11 00:25:30 +10:00
Ross Bencina
5820c8b144 wmme: remove unused parameter. fixes warning. 2016-09-11 00:18:17 +10:00
Ross Bencina
222cd2565f wmme: fix int/char conversion warning 2016-09-11 00:16:05 +10:00
Ross Bencina
cd0eb352f3 wmme: document default device selection algorithm 2016-09-11 00:14:49 +10:00
Ross Bencina
0b3bd90bad Fix signed/unsigned mismatch (wmme device ids are unsigned) and improve semantics by explicitly setting the fallback default device to WAVE_MAPPER (which has value -1). 2016-09-10 23:57:57 +10:00
Ross Bencina
cb766ba622 initialize uninitialized variables to suppress compiler warnings (values would never have been used 2016-09-10 23:53:04 +10:00
Ross Bencina
042d6c7740 Merge branch 'hotplug' into rb-hotplug-wmme 2016-09-10 23:47:39 +10:00
Ross Bencina
231ecf77c7 hotplug: fix signed/unsigned parameter warning in pa_win_hotplug.c 2016-09-10 23:47:21 +10:00
Ross Bencina
1a95d6790b Cleanup: Check return code from waveOutMessage when polling for preferred devices, don't assume that an OUT parameter is unchanged on error. Move #define DRVM_MAPPER_PREFERRED_GET to top of file with other #defines. 2016-09-10 23:43:30 +10:00
Ross Bencina
0a290773de fix missing new line at end of pa_front.c 2016-09-09 23:48:53 +10:00
Ross Bencina
46f97c7c20 Merge branch 'master' into hotplug 2016-09-03 14:19:23 +10:00
Ross Bencina
8e9e507e12 hotplug dsound: initial cut of properly working connectionIds. it works. it pulls device information from each device every time, and then reuses the old connectionId if the GUID is the same. This has the advantage of always pulling updated channel count information. it has the disadvantage of re-polling every device, not just newly inserted/removed ones. the alternative is to reuse the existing device information by copying it over, which could be done. 2016-09-03 01:57:29 +10:00
Ross Bencina
8d285faa5e hotplug dsound: remove resolved fixme comment 2016-09-03 00:30:18 +10:00
Ross Bencina
6840cda9ad hotplug: extended doc comments for ScanDeviceInfos, CommitDeviceInfos, DisposeDeviceInfos. Added requirement that commit and dispose should be implemented such that they can't fail (this is currently the case with DirectSound and WDM/KS implementations). 2016-09-03 00:11:56 +10:00
Ross Bencina
5c5d4fb711 hotplug directsound: factor out freeing of device info array to FreeDeviceInfos. Delete some unused variables. 2016-09-02 23:29:20 +10:00
Ross Bencina
9562fb8074 hotplug: further refinement of API doc comments 2016-09-02 21:47:43 +10:00
Ross Bencina
ec5b0687a8 fix .def file: rename Pa_RefreshDeviceList --> Pa_UpdateAvailableDeviceList 2016-09-02 21:12:10 +10:00
Ross Bencina
b45ec9ed0a Moved hotplug function prototypes from random locations to new header file src/common/pa_hotplug.h. Added draft documentation and licence headers. In pa_front.c moved devices changed code next to refresh device list code. 2016-09-02 20:52:42 +10:00
Ross Bencina
d41ba780b2 hotplug: added connectionId parameter to PaDeviceInfo and bumped struct version number. added PaUtil_MakeDeviceConnectionId() function and used it to initialize connectionId in all host APIs. updated patest_refresh_device_list to display connection id. NOTE: persistent connection ids have not been implemented in dsound and wdmks, so refreshing the device list will change connection ids. 2016-09-02 20:27:39 +10:00
Ross Bencina
abfcd708b4 hotplug: first cut doc comments for PaDevicesChangedCallback and Pa_SetDevicesChangedCallback 2016-09-02 19:43:27 +10:00
Ross Bencina
3f7c315f22 hotplug: rename Pa_UpdateAvailableDeviceList() to Pa_RefreshDeviceList(). Clean up doc comment for Pa_RefreshDeviceList(). 2016-09-02 19:27:47 +10:00
Ross Bencina
9761960aa5 wdmks hotplug: Make CommitDeviceInfos fail-safe by removing allocation of temporary. Factored out device info array freeing function from DisposeDeviceInfos into separate FreeHostApiDeviceInfos. 2016-09-02 19:08:39 +10:00
Ross Bencina
23b4bdd49a wdm/ks hotplug: fixed two bugs in CommitDeviceInfos() Firstly, hostApi->info.deviceCount was being zeroed before it was used to specify how many devices to free, so zero devices/filters would be freed. Secondly, a ptr ptr (**) to temporary scan results localScanResults was being pased to DisposeDeviceInfos() instead of a ptr (*). 2016-09-02 18:52:22 +10:00
Ross Bencina
89f8f75a27 Merge of old SVN hotplug branch into current git HEAD. Includes hotplug support for DirectSound, WDM/KS. Both are working running in debugger but WDM/KS seems to be crashing in stand-alone build. I removed the use of struct _PaInternalInfo in hotplug/pa_front.c to reduce the size of the change. pa_win_dsound.c involved the most intricate changes due to other changes that had been made in the mean time. I've also initialized function ptrs ScanDeviceInfos, CommitDeviceInfos, DisposeDeviceInfos so this should build on all platforms. 2016-08-27 17:51:40 +10:00
21 changed files with 2418 additions and 627 deletions

View file

@ -3,7 +3,7 @@ EXPORTS
;
Pa_GetVersion @1
Pa_GetVersionText @2
Pa_GetErrorText @3
Pa_GetErrorText @3
Pa_Initialize @4
Pa_Terminate @5
Pa_GetHostApiCount @6
@ -35,6 +35,8 @@ Pa_GetStreamReadAvailable @31
Pa_GetStreamWriteAvailable @32
Pa_GetSampleSize @33
Pa_Sleep @34
Pa_RefreshDeviceList @35
Pa_SetDevicesChangedCallback @36
PaAsio_GetAvailableBufferSizes @50
PaAsio_ShowControlPanel @51
PaUtil_InitializeX86PlainConverters @52

View file

@ -1827,6 +1827,10 @@
RelativePath="..\..\src\os\win\pa_win_wdmks_utils.c"
>
</File>
<File
RelativePath="..\..\src\os\win\pa_win_hotplug.c"
>
</File>
<File
RelativePath="..\..\src\os\win\pa_x86_plain_converters.c"
>

View file

@ -155,9 +155,15 @@ const char *Pa_GetErrorText( PaError errorCode );
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
If Pa_Initialize() returns an error code, Pa_Terminate() should
NOT be called.
@note The device list returned by Pa_GetDeviceCount() et al. is frozen
when Pa_Initialize() is called. The device list is not automatically updated
when hardware devices are connected or disconnected. To refresh the list of devices,
either call Pa_RefreshDeviceList() or uninitialize PortAudio using Pa_Terminate(),
and then reinitialize it by calling Pa_Initialize().
@return paNoError if successful, otherwise an error code indicating the cause
of failure.
@ -432,6 +438,69 @@ PaDeviceIndex Pa_GetDefaultInputDevice( void );
PaDeviceIndex Pa_GetDefaultOutputDevice( void );
/** Refresh the list devices to match currently available native audio devices.
PortAudio's list of devices is usually frozen when Pa_Initialize() is
called. Call Pa_RefreshDeviceList() to refresh PortAudio's device list
at any time while PortAudio is initialized.
@return an error code indicating whether the refresh was successful.
If the function succeeds, future calls to Pa_GetDeviceCount() may return
a different count than before, all device indexes may refer to
different devices, and any previously returned PaDeviceInfo pointers are
invalidated.
If the function fails, the device list is unchanged.
@note Open streams will not be affected by calls to this function.
*/
PaError Pa_RefreshDeviceList( void );
/** Functions of type PaDevicesChangedCallback are implemented by PortAudio
clients. They can be registered with PortAudio using the Pa_SetDevicesChangedCallback
function. Once registered they are called when available native audio devices
may have changed. For example, when a USB audio device is connected or disconnected.
When the callback is invoked, there is no change to the devices listed by
PortAudio. To update PortAudio's view of the device list, the client
must call Pa_RefreshDeviceList(). This must not be done from within
the callback.
The callback may occur on any thread. The client should not call other PortAudio
functions from within a PaDevicesChangedCallback. In particular, the client
should not directly call Pa_RefreshDeviceList() from within the notification
callback. Instead, the client should set an atomic flag, or queue a message to
notify a controller thread that would then call Pa_RefreshDeviceList().
@param userData The userData parameter supplied to Pa_SetDevicesChangedCallback()
@see Pa_SetDevicesChangedCallback
*/
typedef void PaDevicesChangedCallback( void *userData );
/** Register a callback function that will be called when native audio devices
become available or unavailable. See the description of PaDevicesChangedCallback
for further details about when the callback will be called.
@param userData A client supplied pointer that is passed to the devices changed
callback.
@param devicesChangedCallback a pointer to a function with the same signature
as PaDevicesChangedCallback, which will be called when native devices become
available or unavailable. Passing NULL for this parameter will un-register a
previously registered devices changed callback function.
@return on success returns paNoError, otherwise an error code indicating the
cause of the error.
@see PaStreamFinishedCallback
*/
PaError Pa_SetDevicesChangedCallback( void *userData, PaDevicesChangedCallback* devicesChangedCallback );
/** The type used to represent monotonic time in seconds. PaTime is
used for the fields of the PaStreamCallbackTimeInfo argument to the
PaStreamCallback and as the result of Pa_GetStreamTime().
@ -477,12 +546,30 @@ typedef unsigned long PaSampleFormat;
#define paNonInterleaved ((PaSampleFormat) 0x80000000) /**< @see PaSampleFormat */
/** An integer id that uniquely identifies a device, so long as the device is
plugged in and PortAudio is initialized. Its purpose is to identify (correlate)
devices across calls to Pa_RefreshDeviceList().
Each device reported by Pa_GetDeviceInfo() has a distinct connectionId.
PortAudio makes a best-effort to ensure that a persistent underlying device
has the same connectionId before and after a single call to Pa_RefreshDeviceList().
If a device is disconnected and later reconnected it may be assigned a new
connectionId.
@see Pa_RefreshDeviceList()
*/
typedef unsigned long PaDeviceConnectionId;
/** 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 */
int structVersion; /* this is struct version 3 */
const char *name;
PaHostApiIndex hostApi; /**< note this is a host API index, not a type id*/
@ -497,6 +584,8 @@ typedef struct PaDeviceInfo
PaTime defaultHighOutputLatency;
double defaultSampleRate;
PaDeviceConnectionId connectionId; /**< @see PaDeviceConnectionId */
} PaDeviceInfo;
@ -1201,7 +1290,6 @@ PaError Pa_GetSampleSize( PaSampleFormat format );
void Pa_Sleep( long msec );
#ifdef __cplusplus
}
#endif /* __cplusplus */

View file

@ -76,11 +76,13 @@
#include "pa_stream.h"
#include "pa_trace.h" /* still usefull?*/
#include "pa_debugprint.h"
#include "pa_hotplug.h"
#ifndef PA_SVN_REVISION
#include "pa_svnrevision.h"
#endif
/**
* This is incremented if we make incompatible API changes.
* This version scheme is based loosely on http://semver.org/
@ -161,6 +163,8 @@ static int deviceCount_ = 0;
PaUtilStreamRepresentation *firstOpenStream_ = NULL;
PaDevicesChangedCallback* devicesChangedCallback_ = NULL;
void* devicesChangedCallbackUserData_ = NULL;
#define PA_IS_INITIALISED_ (initializationCount_ != 0)
@ -371,6 +375,9 @@ PaError Pa_Initialize( void )
PaUtil_InitializeClock();
PaUtil_ResetTraceMessages();
/* Initialize hot plug here, so all its internal info is setup */
PaUtil_InitializeHotPlug();
result = InitializeHostApis();
if( result == paNoError )
++initializationCount_;
@ -396,6 +403,8 @@ PaError Pa_Terminate( void )
TerminateHostApis();
PaUtil_TerminateHotPlug();
PaUtil_DumpTraceMessages();
}
result = paNoError;
@ -744,6 +753,152 @@ PaDeviceIndex Pa_GetDefaultOutputDevice( void )
}
PaError Pa_RefreshDeviceList( void )
{
PaError result = paNoError;
void **scanResults = NULL;
int *deviceCounts = NULL;
int i = 0;
PA_LOGAPI_ENTER( "Pa_UpdateAvailableDeviceList" );
if( !PA_IS_INITIALISED_ )
{
result = paNotInitialized;
goto done;
}
/* Allocate data structures used in 2-stage commit */
scanResults = (void **) PaUtil_AllocateMemory( sizeof(void*) * hostApisCount_ );
if( !scanResults )
{
result = paInsufficientMemory;
goto done;
}
deviceCounts = ( int * ) PaUtil_AllocateMemory( sizeof( int ) * hostApisCount_ );
if( !deviceCounts )
{
result = paInsufficientMemory;
goto done;
}
/* Phase 1: Perform a scan of new devices */
for( i = 0 ; i < hostApisCount_ ; ++i )
{
PaUtilHostApiRepresentation *hostApi = hostApis_[i];
if( hostApi->ScanDeviceInfos == NULL )
continue;
PA_DEBUG(( "Scanning new device list for host api %d.\n",i));
if( hostApi->ScanDeviceInfos( hostApi, i, &scanResults[ i ], &deviceCounts[ i ] ) != paNoError )
break;
}
/* Check the result of the scan operation */
if( i < hostApisCount_ )
{
/* If failure, rollback the scan changes back to original state */
int j = 0;
for( j = 0 ; j < i ; ++j )
{
PaUtilHostApiRepresentation *hostApi = hostApis_[j];
if( hostApi->DisposeDeviceInfos == NULL )
continue;
PA_DEBUG(( "Performing rollback for device list scan for host api %d.\n",i));
hostApi->DisposeDeviceInfos( hostApi, scanResults[ j ], deviceCounts[ j ] );
}
}
else
{
int baseDeviceIndex = 0;
deviceCount_ = 0;
/* Otherwise, commit the scan changes to each back-end */
for( i = 0 ; i < hostApisCount_ ; ++i )
{
PaUtilHostApiRepresentation *hostApi = hostApis_[i];
if( hostApi->CommitDeviceInfos == NULL )
{
/* Not yet implemented for this backend. Just
assume that the baseDeviceIndex and the deviceCount_ are
incremented according to the values in the info */
baseDeviceIndex += hostApi->info.deviceCount;
deviceCount_ += hostApi->info.deviceCount;
continue;
}
PA_DEBUG(( "Committing device list scan for host api %d.\n",i));
if( hostApi->CommitDeviceInfos( hostApi, i, scanResults[ i ], deviceCounts[ i ] ) != paNoError )
{
PA_DEBUG(( "Committing failed (shouldn't happen) %d.\n",i));
result = paInternalError;
goto done;
}
assert( hostApi->info.defaultInputDevice < hostApi->info.deviceCount );
assert( hostApi->info.defaultOutputDevice < hostApi->info.deviceCount );
hostApi->privatePaFrontInfo.baseDeviceIndex = baseDeviceIndex;
if( hostApi->info.defaultInputDevice != paNoDevice )
hostApi->info.defaultInputDevice += baseDeviceIndex;
if( hostApi->info.defaultOutputDevice != paNoDevice )
hostApi->info.defaultOutputDevice += baseDeviceIndex;
baseDeviceIndex += hostApi->info.deviceCount;
deviceCount_ += hostApi->info.deviceCount;
}
}
done:
if( scanResults )
PaUtil_FreeMemory( scanResults );
if( deviceCounts )
PaUtil_FreeMemory( deviceCounts );
return result;
}
PaError Pa_SetDevicesChangedCallback( void *userData, PaStreamFinishedCallback* devicesChangedCallback )
{
PaUtil_LockHotPlug();
devicesChangedCallback_ = devicesChangedCallback;
devicesChangedCallbackUserData_ = userData;
PaUtil_UnlockHotPlug();
return paNoError;
}
/* Called by platform hotplug implementation whenever a OS audio device change has been detected */
void PaUtil_DevicesChanged(unsigned state, void* pData)
{
(void)state;
(void)pData;
PaUtil_LockHotPlug();
if (devicesChangedCallback_)
{
(devicesChangedCallback_)(devicesChangedCallbackUserData_);
}
PaUtil_UnlockHotPlug();
}
static PaDeviceConnectionId nextDeviceConnectionId_ = 1000;
PaDeviceConnectionId PaUtil_MakeDeviceConnectionId( void )
{
/* FIXME: if the counter wraps around we may want to ensure that
we are not issuing an ID that belongs to a current device.
we need to honor the invariant "no two devices have the same connection id"
*/
return nextDeviceConnectionId_++;
}
const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device )
{
int hostSpecificDeviceIndex;
@ -1806,4 +1961,3 @@ PaError Pa_GetSampleSize( PaSampleFormat format )
return (PaError) result;
}

View file

@ -171,6 +171,9 @@ typedef struct PaUtilPrivatePaFrontHostApiInfo {
}PaUtilPrivatePaFrontHostApiInfo;
PaDeviceConnectionId PaUtil_MakeDeviceConnectionId( void );
/** 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
@ -322,6 +325,119 @@ typedef struct PaUtilHostApiRepresentation {
const PaStreamParameters *inputParameters,
const PaStreamParameters *outputParameters,
double sampleRate );
/** ScanDeviceInfos, CommitDeviceInfos, DisposeDeviceInfos are used to refresh the device list.
You can think of the caller doing something like:
void *scanResults = 0;
int newDeviceCount = 0;
if( hostApi->ScanDeviceInfos(hostApi, hostApiIndex, &scanResults, &newDeviceCount) == paNoError )
{
... do other stuff ...
if( ok to commit )
{
hostApi->CommitDeviceInfos(hostApi, hostApiIndex, scanResults, newDeviceCount);
}
else
{
hostApi->DisposeDeviceInfos(hostApi, hostApiIndex, scanResults, newDeviceCount);
}
}
NB: ScanDeviceInfos may be called on all host APIs first, prior to pa_front
deciding whether to commit or cleanup.
*/
/**
Scan for current native device information and compute a new device count.
Preparation step of a two-stage transaction that updates the active device list.
@param hostApi The target host API.
@param index The host API index of the target host API.
@param scanResults (OUT) Result parameter. Upon success, set to point to an
opaque structure containing collected device information. Ignored on failure.
@param newDeviceCount (Out) Result parameter. Upon success, set to the
number of discovered devices. Ignored on failure.
@return on success returns paNoError, otherwise an error code indicating
the cause of the error.
This function should not make any visible changes to the host API's internal
state. In particular, it should not update the active device list, device
information or device count.
If the function returns successfully, the caller will either (1) pass
scanResults and newDeviceCount to CommitDeviceInfos() to complete the
transaction, or (2) pass scanResults and newDeviceCount to
DisposeDeviceInfos() to abort the transaction.
@note CommitDeviceInfos and DisposeDeviceInfos should not fail, therefore
ScanDeviceInfos should perform all necessary preparations,
such as memory allocation, so that CommitDeviceInfos can always execute
without failure.
@see CommitDeviceInfos, DisposeDeviceInfos
*/
PaError (*ScanDeviceInfos)( struct PaUtilHostApiRepresentation *hostApi,
PaHostApiIndex index,
void **scanResults,
int *newDeviceCount );
/**
Update the active device list with new device information that was
returned by ScanDeviceInfos(). This is the final commit step of a two-stage
transaction that updates the active device list.
As this function is not permitted to fail, it should be very simple:
swap in new device information and free the old information.
@param hostApi The target host API.
@param index The host API index of the target host API.
@param scanResults An opaque structure containing collected device
information previously returned by ScanDeviceInfos. This is the information
that will be installed into the active device list.
@param newDeviceCount The number of discovered devices previously returned
by ScanDeviceInfos. This is the new number of devices in the active device
list.
@note This function is not permitted to fail. It should always return paNoError.
@see ScanDeviceInfos, DisposeDeviceInfos
*/
PaError (*CommitDeviceInfos)( struct PaUtilHostApiRepresentation *hostApi,
PaHostApiIndex index,
void *scanResults,
int deviceCount );
/**
Free device information that was returned by ScanDeviceInfos. This is
used in the cleanup process for a failed two-stage transaction.
@param hostApi The target host API.
@param index The host API index of the target host API.
@param scanResults An opaque structure containing collected device
information previously returned by ScanDeviceInfos. scanResults will be
freed by this function.
@param newDeviceCount The number of discovered devices previously returned
by ScanDeviceInfos.
@note This function is not permitted to fail. It should always return paNoError.
@see CommitDeviceInfos, ScanDeviceInfos
*/
PaError (*DisposeDeviceInfos)( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount );
} PaUtilHostApiRepresentation;

98
src/common/pa_hotplug.h Normal file
View file

@ -0,0 +1,98 @@
#ifndef PA_HOTPLUG_H
#define PA_HOTPLUG_H
/*
* $Id$
* Portable Audio I/O Library
* hotplug interface and utilities
*
* Based on the Open Source API proposed by Ross Bencina
* Copyright (c) 1999-2016 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.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
/** @file
@ingroup common_src
@brief Utilities for implementing hotplug support.
*/
#include "portaudio.h"
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
/** Init/terminate hotplug notification engine.
At the moment there is on hotplug implementation per platform.
It is responsible for posting devices changed notifications
by calling PaUtil_DevicesChanged.
Once we support multiple notification mechanisms we'll probably
have the host APIs init and terminate their own notification
engines (using reference counting?) e.g. wasapi will have its own,
but other windows APIs will use the global windows notifier.
Implemented in pa_win_hotplug.c
*/
void PaUtil_InitializeHotPlug();
void PaUtil_TerminateHotPlug();
/** Invoke the client's registered devices changed notification.
@param first 0 = unknown, 1 = insertion, 2 = removal
@param second Host specific device change info (in windows it is the (unicode) device path)
Parameters are currently ignored. TODO REVIEW
Implemented in pa_front.c
*/
void PaUtil_DevicesChanged(unsigned, void*);
/** Lock/unlock a mutex used to protect the devices changed callback.
Used by pa_front.c to synchronise notification callbacks and client
requests to set/clear the device callback.
Coded like this because we don't have a cross-platform mutex.
Implemented in pa_win_hotplug.c
*/
void PaUtil_LockHotPlug();
void PaUtil_UnlockHotPlug();
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* PA_HOTPLUG_H */

View file

@ -757,6 +757,9 @@ PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
(*hostApi)->ScanDeviceInfos = 0;
(*hostApi)->CommitDeviceInfos = 0;
(*hostApi)->DisposeDeviceInfos = 0;
/** If AlsaErrorHandler is to be used, do not forget to unregister callback pointer in
Terminate function.
@ -1186,9 +1189,11 @@ static PaError FillInDevInfo( PaAlsaHostApiRepresentation *alsaApi, HwDevInfo* d
}
}
baseDeviceInfo->structVersion = 2;
baseDeviceInfo->structVersion = 3;
baseDeviceInfo->hostApi = alsaApi->hostApiIndex;
baseDeviceInfo->name = deviceHwInfo->name;
deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId();
devInfo->alsaName = deviceHwInfo->alsaName;
devInfo->isPlug = deviceHwInfo->isPlug;

View file

@ -636,7 +636,7 @@ static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostA
hpiDevice->streamIndex = j;
hpiDevice->streamIsOutput = 0;
/* Set common PortAudio device stats */
baseDeviceInfo->structVersion = 2;
baseDeviceInfo->structVersion = 3;
/* Make sure name string is owned by API info structure */
sprintf( srcName,
"Adapter %d (%4X) - Input Stream %d", i+1, type, j+1 );
@ -656,6 +656,7 @@ static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostA
/* HPI interface can actually handle any sampling rate to 1 Hz accuracy,
* so this default is as good as any */
baseDeviceInfo->defaultSampleRate = 44100;
baseDeviceInfo->connectionId = PaUtil_MakeDeviceConnectionId();
/* Store device in global PortAudio list */
hostApi->deviceInfos[deviceIndex++] = (PaDeviceInfo *) hpiDevice;
@ -678,7 +679,7 @@ static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostA
hpiDevice->streamIndex = j;
hpiDevice->streamIsOutput = 1;
/* Set common PortAudio device stats */
baseDeviceInfo->structVersion = 2;
baseDeviceInfo->structVersion = 3;
/* Make sure name string is owned by API info structure */
sprintf( srcName,
"Adapter %d (%4X) - Output Stream %d", i+1, type, j+1 );
@ -698,6 +699,7 @@ static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostA
/* HPI interface can actually handle any sampling rate to 1 Hz accuracy,
* so this default is as good as any */
baseDeviceInfo->defaultSampleRate = 44100;
baseDeviceInfo->connectionId = PaUtil_MakeDeviceConnectionId();
/* Store device in global PortAudio list */
hostApi->deviceInfos[deviceIndex++] = (PaDeviceInfo *) hpiDevice;
@ -771,6 +773,9 @@ PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
(*hostApi)->ScanDeviceInfos = 0;
(*hostApi)->CommitDeviceInfos = 0;
(*hostApi)->DisposeDeviceInfos = 0;
PaUtil_InitializeStreamInterface( &hpiHostApi->callbackStreamInterface, CloseStream, StartStream,
StopStream, AbortStream, IsStreamStopped, IsStreamActive,

View file

@ -1322,10 +1322,11 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex
PaAsioDeviceInfo *asioDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ];
PaDeviceInfo *deviceInfo = &asioDeviceInfo->commonDeviceInfo;
deviceInfo->structVersion = 2;
deviceInfo->structVersion = 3;
deviceInfo->hostApi = hostApiIndex;
deviceInfo->name = names[i];
deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId();
if( InitPaDeviceInfoFromAsioDriver( asioHostApi, names[i], i, deviceInfo, asioDeviceInfo ) == paNoError )
{
@ -1356,6 +1357,9 @@ PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
(*hostApi)->ScanDeviceInfos = 0;
(*hostApi)->CommitDeviceInfos = 0;
(*hostApi)->DisposeDeviceInfos = 0;
PaUtil_InitializeStreamInterface( &asioHostApi->callbackStreamInterface, CloseStream, StartStream,
StopStream, AbortStream, IsStreamStopped, IsStreamActive,

View file

@ -659,7 +659,7 @@ static PaError InitializeDeviceInfo( PaMacAUHAL *auhalHostApi,
memset(deviceInfo, 0, sizeof(PaDeviceInfo));
deviceInfo->structVersion = 2;
deviceInfo->structVersion = 3;
deviceInfo->hostApi = hostApiIndex;
/* Get the device name using CFString */
@ -693,6 +693,7 @@ static PaError InitializeDeviceInfo( PaMacAUHAL *auhalHostApi,
CFRelease(nameRef);
}
deviceInfo->name = name;
deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId();
/* Try to get the default sample rate. Don't fail if we can't get this. */
propSize = sizeof(Float64);
@ -825,6 +826,9 @@ PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIn
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
(*hostApi)->ScanDeviceInfos = 0;
(*hostApi)->CommitDeviceInfos = 0;
(*hostApi)->DisposeDeviceInfos = 0;
PaUtil_InitializeStreamInterface( &auhalHostApi->callbackStreamInterface,
CloseStream, StartStream,

File diff suppressed because it is too large Load diff

View file

@ -573,7 +573,7 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
strlen(client_names[client_index]) + 1 ), paInsufficientMemory );
strcpy( (char *)curDevInfo->name, client_names[client_index] );
curDevInfo->structVersion = 2;
curDevInfo->structVersion = 3;
curDevInfo->hostApi = jackApi->hostApiIndex;
/* JACK is very inflexible: there is one sample rate the whole
@ -634,6 +634,8 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
commonApi->info.defaultInputDevice = client_index;
if( commonApi->info.defaultOutputDevice == paNoDevice && curDevInfo->maxOutputChannels > 0 )
commonApi->info.defaultOutputDevice = client_index;
deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId();
}
error:
@ -756,6 +758,9 @@ PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi,
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
(*hostApi)->ScanDeviceInfos = 0;
(*hostApi)->CommitDeviceInfos = 0;
(*hostApi)->DisposeDeviceInfos = 0;
PaUtil_InitializeStreamInterface( &jackHostApi->callbackStreamInterface,
CloseStream, StartStream,

View file

@ -256,6 +256,9 @@ PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
(*hostApi)->ScanDeviceInfos = 0;
(*hostApi)->CommitDeviceInfos = 0;
(*hostApi)->DisposeDeviceInfos = 0;
PA_ENSURE( BuildDeviceList( ossHostApi ) );
@ -295,7 +298,7 @@ PaError PaUtil_InitializeDeviceInfo( PaDeviceInfo *deviceInfo, const char *name,
{
PaError result = paNoError;
deviceInfo->structVersion = 2;
deviceInfo->structVersion = 3;
if( allocations )
{
size_t len = strlen( name ) + 1;
@ -313,6 +316,7 @@ PaError PaUtil_InitializeDeviceInfo( PaDeviceInfo *deviceInfo, const char *name,
deviceInfo->defaultHighInputLatency = defaultHighInputLatency;
deviceInfo->defaultHighOutputLatency = defaultHighOutputLatency;
deviceInfo->defaultSampleRate = defaultSampleRate;
deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId();
error:
return result;

View file

@ -209,6 +209,9 @@ PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiI
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
(*hostApi)->ScanDeviceInfos = 0;
(*hostApi)->CommitDeviceInfos = 0;
(*hostApi)->DisposeDeviceInfos = 0;
PaUtil_InitializeStreamInterface( &skeletonHostApi->callbackStreamInterface, CloseStream, StartStream,
StopStream, AbortStream, IsStreamStopped, IsStreamActive,

View file

@ -1563,7 +1563,7 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
for (i = 0; i < paWasapi->deviceCount; ++i)
{
PaDeviceInfo *deviceInfo = &deviceInfoArray[i];
deviceInfo->structVersion = 2;
deviceInfo->structVersion = 3;
deviceInfo->hostApi = hostApiIndex;
PA_DEBUG(("WASAPI: device idx: %02d\n", i));
@ -1796,6 +1796,8 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
break;
}
deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId();
(*hostApi)->deviceInfos[i] = deviceInfo;
++(*hostApi)->info.deviceCount;
}
@ -1804,6 +1806,9 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
(*hostApi)->ScanDeviceInfos = 0;
(*hostApi)->CommitDeviceInfos = 0;
(*hostApi)->DisposeDeviceInfos = 0;
PaUtil_InitializeStreamInterface( &paWasapi->callbackStreamInterface, CloseStream, StartStream,
StopStream, AbortStream, IsStreamStopped, IsStreamActive,

View file

@ -3442,6 +3442,22 @@ static unsigned GetNameIndex(PaNameHashObject* obj, const wchar_t* name, const B
return 0;
}
static void FreeHostApiDeviceInfos( PaUtilAllocationGroup *allocations, PaDeviceInfo **deviceInfos, int deviceCount )
{
int i;
for (i = 0; i < deviceCount; ++i)
{
PaWinWdmDeviceInfo* pDevice = (PaWinWdmDeviceInfo*)deviceInfos[i];
if (pDevice->filter != 0)
{
FilterFree(pDevice->filter);
}
}
PaUtil_GroupFreeMemory( allocations, deviceInfos[0] ); /* all device info structs are allocated in a block so we can destroy them here */
PaUtil_GroupFreeMemory( allocations, deviceInfos );
}
static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, void **scanResults, int *newDeviceCount )
{
PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
@ -3514,7 +3530,7 @@ static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaH
for( i = 0 ; i < totalDeviceCount; ++i )
{
PaDeviceInfo *deviceInfo = &deviceInfoArray[i].inheritedDeviceInfo;
deviceInfo->structVersion = 2;
deviceInfo->structVersion = 3;
deviceInfo->hostApi = hostApiIndex;
deviceInfo->name = 0;
outArgument->deviceInfos[ i ] = deviceInfo;
@ -3557,11 +3573,13 @@ static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaH
wdmDeviceInfo->filter = pFilter;
deviceInfo->structVersion = 2;
deviceInfo->structVersion = 3;
deviceInfo->hostApi = hostApiIndex;
deviceInfo->name = wdmDeviceInfo->compositeName;
/* deviceInfo->hostApiSpecificDeviceInfo = &pFilter->devInfo; */
deviceInfo->connectionId = PaUtil_MakeDeviceConnectionId();
wdmDeviceInfo->pin = pin->pinId;
/* Get the name of the "device" */
@ -3706,22 +3724,17 @@ static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, P
{
PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
hostApi->info.deviceCount = 0;
hostApi->info.defaultInputDevice = paNoDevice;
hostApi->info.defaultOutputDevice = paNoDevice;
/* Free any old memory which might be in the device info */
if( hostApi->deviceInfos )
{
PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory(
wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults));
localScanResults->deviceInfos = hostApi->deviceInfos;
DisposeDeviceInfos(hostApi, &localScanResults, hostApi->info.deviceCount);
FreeHostApiDeviceInfos( wdmHostApi->allocations, hostApi->deviceInfos, hostApi->info.deviceCount );
hostApi->deviceInfos = NULL;
}
hostApi->info.deviceCount = 0;
hostApi->info.defaultInputDevice = paNoDevice;
hostApi->info.defaultOutputDevice = paNoDevice;
if( scanResults != NULL )
{
PaWinWDMScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinWDMScanDeviceInfosResults * ) scanResults;
@ -3746,7 +3759,7 @@ static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, P
static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount )
{
PaWinWdmHostApiRepresentation *winDsHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
if( scanResults != NULL )
{
@ -3754,21 +3767,10 @@ static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi,
if( scanDeviceInfosResults->deviceInfos )
{
int i;
for (i = 0; i < deviceCount; ++i)
{
PaWinWdmDeviceInfo* pDevice = (PaWinWdmDeviceInfo*)scanDeviceInfosResults->deviceInfos[i];
if (pDevice->filter != 0)
{
FilterFree(pDevice->filter);
}
}
PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos[0] ); /* all device info structs are allocated in a block so we can destroy them here */
PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos );
FreeHostApiDeviceInfos( wdmHostApi->allocations, scanDeviceInfosResults->deviceInfos, hostApi->info.deviceCount );
}
PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults );
PaUtil_GroupFreeMemory( wdmHostApi->allocations, scanDeviceInfosResults );
}
return paNoError;
@ -3853,11 +3855,10 @@ PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
/* In preparation for hotplug
(*hostApi)->ScanDeviceInfos = ScanDeviceInfos;
(*hostApi)->CommitDeviceInfos = CommitDeviceInfos;
(*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos;
*/
PaUtil_InitializeStreamInterface( &wdmHostApi->callbackStreamInterface, CloseStream, StartStream,
StopStream, AbortStream, IsStreamStopped, IsStreamActive,
GetStreamTime, GetStreamCpuLoad,
@ -3900,10 +3901,11 @@ static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
if( wdmHostApi)
{
PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory(
wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults));
localScanResults->deviceInfos = hostApi->deviceInfos;
DisposeDeviceInfos(hostApi, localScanResults, hostApi->info.deviceCount);
if( hostApi->deviceInfos )
{
FreeHostApiDeviceInfos( wdmHostApi->allocations, hostApi->deviceInfos, hostApi->info.deviceCount );
hostApi->deviceInfos = NULL;
}
if( wdmHostApi->allocations )
{

File diff suppressed because it is too large Load diff

348
src/os/win/pa_win_hotplug.c Normal file
View file

@ -0,0 +1,348 @@
/*
* $Id$
* Portable Audio I/O Library
* Hotplug interface and utilities
* Copyright (c) 2011-2016 Robert Bielik
*
* Based on the Open Source API proposed by Ross Bencina
* Copyright (c) 1999-2016 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.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#include "pa_hotplug.h"
#include "pa_util.h"
#include "pa_debugprint.h"
#include "pa_allocation.h"
#include "pa_win_wdmks_utils.h"
#include <windows.h>
#include <dbt.h>
#include <process.h>
#include <assert.h>
#include <ks.h>
#include <ksmedia.h>
#include <setupapi.h>
#include <stdio.h>
#if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */
#pragma comment( lib, "setupapi.lib" )
#endif
/* use CreateThread for CYGWIN/Windows Mobile, _beginthreadex for all others */
#if !defined(__CYGWIN__) && !defined(_WIN32_WCE)
#define CREATE_THREAD_FUNCTION (HANDLE)_beginthreadex
#define PA_THREAD_FUNC static unsigned WINAPI
#else
#define CREATE_THREAD_FUNCTION CreateThread
#define PA_THREAD_FUNC static DWORD WINAPI
#endif
typedef struct PaHotPlugDeviceInfo
{
wchar_t name[MAX_PATH];
struct PaHotPlugDeviceInfo* next;
} PaHotPlugDeviceInfo;
typedef struct PaHotPlugDeviceEventHandlerInfo
{
HANDLE hWnd;
HANDLE hMsgThread;
HANDLE hNotify;
CRITICAL_SECTION lock;
PaUtilAllocationGroup* cacheAllocGroup;
PaHotPlugDeviceInfo* cache;
} PaHotPlugDeviceEventHandlerInfo;
static BOOL RemoveDeviceFromCache(PaHotPlugDeviceEventHandlerInfo* pInfo, const wchar_t* name)
{
if (pInfo->cache != NULL)
{
PaHotPlugDeviceInfo* lastEntry = 0;
PaHotPlugDeviceInfo* entry = pInfo->cache;
while (entry != NULL)
{
if (_wcsicmp(entry->name, name) == 0)
{
if (lastEntry)
{
lastEntry->next = entry->next;
}
else
{
pInfo->cache = NULL;
}
PaUtil_GroupFreeMemory(pInfo->cacheAllocGroup, entry);
return TRUE;
}
lastEntry = entry;
entry = entry->next;
}
}
return FALSE;
}
static void InsertDeviceIntoCache(PaHotPlugDeviceEventHandlerInfo* pInfo, const wchar_t* name)
{
PaHotPlugDeviceInfo** ppEntry = NULL;
/* Remove it first (if possible) so we don't accidentally get duplicates */
RemoveDeviceFromCache(pInfo, name);
if (pInfo->cache == NULL)
{
ppEntry = &pInfo->cache;
}
else
{
PaHotPlugDeviceInfo* entry = pInfo->cache;
while (entry->next != NULL)
{
entry = entry->next;
}
ppEntry = &entry->next;
}
*ppEntry = (PaHotPlugDeviceInfo*)PaUtil_GroupAllocateMemory(pInfo->cacheAllocGroup, sizeof(PaHotPlugDeviceInfo));
wcsncpy((*ppEntry)->name, name, MAX_PATH-1);
(*ppEntry)->next = NULL;
}
static BOOL IsDeviceAudio(const wchar_t* deviceName)
{
int channelCnt = 0;
channelCnt += PaWin_WDMKS_QueryFilterMaximumChannelCount((void*)deviceName, 1);
channelCnt += PaWin_WDMKS_QueryFilterMaximumChannelCount((void*)deviceName, 0);
return (channelCnt > 0);
}
static void PopulateCacheWithAvailableAudioDevices(PaHotPlugDeviceEventHandlerInfo* pInfo)
{
HDEVINFO handle = NULL;
const int sizeInterface = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W) + (MAX_PATH * sizeof(WCHAR));
SP_DEVICE_INTERFACE_DETAIL_DATA_W* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA_W*)PaUtil_AllocateMemory(sizeInterface);
if (devInterfaceDetails)
{
devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
/* Open a handle to search for devices (filters) */
handle = SetupDiGetClassDevsW(&KSCATEGORY_AUDIO,NULL,NULL,DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if( handle != NULL )
{
int device;
/* Iterate through the devices */
for( device = 0;;device++ )
{
SP_DEVICE_INTERFACE_DATA interfaceData;
SP_DEVINFO_DATA devInfoData;
int noError;
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
interfaceData.Reserved = 0;
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
devInfoData.Reserved = 0;
noError = SetupDiEnumDeviceInterfaces(handle,NULL,&KSCATEGORY_AUDIO,device,&interfaceData);
if( !noError )
break; /* No more devices */
noError = SetupDiGetDeviceInterfaceDetailW(handle,&interfaceData,devInterfaceDetails,sizeInterface,NULL,&devInfoData);
if( noError )
{
if (IsDeviceAudio(devInterfaceDetails->DevicePath))
{
PA_DEBUG(("Hotplug cache populated with: '%S'\n", devInterfaceDetails->DevicePath));
InsertDeviceIntoCache(pInfo, devInterfaceDetails->DevicePath);
}
}
}
SetupDiDestroyDeviceInfoList(handle);
}
PaUtil_FreeMemory(devInterfaceDetails);
}
}
static LRESULT CALLBACK PaMsgWinProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
PaHotPlugDeviceEventHandlerInfo* pInfo = (PaHotPlugDeviceEventHandlerInfo*)( GetWindowLongPtr(hWnd, GWLP_USERDATA) );
switch(msg)
{
case WM_DEVICECHANGE:
switch(wParam)
{
case DBT_DEVICEARRIVAL:
{
PDEV_BROADCAST_DEVICEINTERFACE_W ptr = (PDEV_BROADCAST_DEVICEINTERFACE_W)lParam;
if (ptr->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
break;
if (!IsEqualGUID(&ptr->dbcc_classguid, &KSCATEGORY_AUDIO))
break;
if (IsDeviceAudio(ptr->dbcc_name))
{
PA_DEBUG(("Device inserted : %S\n", ptr->dbcc_name));
InsertDeviceIntoCache(pInfo, ptr->dbcc_name);
PaUtil_DevicesChanged(1, ptr->dbcc_name);
}
}
break;
case DBT_DEVICEREMOVECOMPLETE:
{
PDEV_BROADCAST_DEVICEINTERFACE_W ptr = (PDEV_BROADCAST_DEVICEINTERFACE_W)lParam;
if (ptr->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
break;
if (!IsEqualGUID(&ptr->dbcc_classguid, &KSCATEGORY_AUDIO))
break;
if (RemoveDeviceFromCache(pInfo, ptr->dbcc_name))
{
PA_DEBUG(("Device removed : %S\n", ptr->dbcc_name));
PaUtil_DevicesChanged(2, ptr->dbcc_name);
}
}
break;
default:
break;
}
break;
}
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
PA_THREAD_FUNC PaRunMessageLoop(void* ptr)
{
PaHotPlugDeviceEventHandlerInfo* pInfo = (PaHotPlugDeviceEventHandlerInfo*)ptr;
WNDCLASSW wnd = { 0 };
HMODULE hInstance = GetModuleHandleW(NULL);
wnd.lpfnWndProc = PaMsgWinProcW;
wnd.hInstance = hInstance;
wnd.lpszClassName = L"{1E0D4F5A-B31F-4dcc-AE3C-4F30A47BD521}"; /* Using a GUID as class name */
pInfo->hWnd = CreateWindowW((LPCWSTR)MAKEINTATOM(RegisterClassW(&wnd)), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, hInstance, NULL);
if (pInfo->hWnd)
{
DEV_BROADCAST_DEVICEINTERFACE_W NotificationFilter = { sizeof(DEV_BROADCAST_DEVICEINTERFACE_W) };
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
#ifndef DEVICE_NOTIFY_ALL_INTERFACE_CLASSES
#define DEVICE_NOTIFY_ALL_INTERFACE_CLASSES 0x00000004
#endif
pInfo->hNotify = RegisterDeviceNotificationW(
pInfo->hWnd,
&NotificationFilter,
DEVICE_NOTIFY_WINDOW_HANDLE|DEVICE_NOTIFY_ALL_INTERFACE_CLASSES
);
assert(pInfo->hNotify);
SetWindowLongPtr(pInfo->hWnd, GWLP_USERDATA, (LONG_PTR)pInfo);
if (pInfo->hNotify)
{
MSG msg;
BOOL result;
while((result = GetMessageW(&msg, pInfo->hWnd, 0, 0)) != 0)
{
if (result == -1)
{
break;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
UnregisterDeviceNotification(pInfo->hNotify);
pInfo->hNotify = 0;
}
DestroyWindow(pInfo->hWnd);
pInfo->hWnd = 0;
}
return 0;
}
static PaHotPlugDeviceEventHandlerInfo* s_handler = 0;
void PaUtil_InitializeHotPlug()
{
if (s_handler == 0)
{
s_handler = (PaHotPlugDeviceEventHandlerInfo*)PaUtil_AllocateMemory(sizeof(PaHotPlugDeviceEventHandlerInfo));
if (s_handler)
{
s_handler->cacheAllocGroup = PaUtil_CreateAllocationGroup();
InitializeCriticalSection(&s_handler->lock);
PopulateCacheWithAvailableAudioDevices(s_handler);
/* Start message thread */
s_handler->hMsgThread = CREATE_THREAD_FUNCTION(NULL, 0, PaRunMessageLoop, s_handler, 0, NULL);
assert(s_handler->hMsgThread != 0);
}
}
}
void PaUtil_TerminateHotPlug()
{
if (s_handler != 0)
{
if (s_handler->hWnd)
{
PostMessage(s_handler->hWnd, WM_QUIT, 0, 0);
if (WaitForSingleObject(s_handler->hMsgThread, 1000) == WAIT_TIMEOUT)
{
TerminateThread(s_handler->hMsgThread, (DWORD)-1);
}
}
DeleteCriticalSection(&s_handler->lock);
PaUtil_FreeAllAllocations( s_handler->cacheAllocGroup );
PaUtil_DestroyAllocationGroup( s_handler->cacheAllocGroup );
PaUtil_FreeMemory( s_handler );
s_handler = 0;
}
}
void PaUtil_LockHotPlug()
{
EnterCriticalSection(&s_handler->lock);
}
void PaUtil_UnlockHotPlug()
{
LeaveCriticalSection(&s_handler->lock);
}

View file

@ -0,0 +1,49 @@
#include <stdio.h>
#include <assert.h>
#include "portaudio.h"
void printDevices()
{
int deviceCount = Pa_GetDeviceCount();
int i;
for( i=0; i < deviceCount; ++i ){
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo( deviceInfo->hostApi );
assert( deviceInfo != 0 );
assert( deviceInfo->structVersion >= 3 ); /* should be the case if all APIs have implemented connectionId */
printf( "%d (conn id: %d) %s (%s)\n", i, deviceInfo->connectionId, deviceInfo->name, hostApiInfo->name );
}
}
static void devicesChangedCallback(void* p)
{
(void)p;
printf( "Portaudio device list have changed!\n" );
}
int main(int argc, char* argv[])
{
Pa_Initialize();
Pa_SetDevicesChangedCallback(NULL, devicesChangedCallback);
for(;;){
printDevices();
printf( "press [enter] to update the device list. or q + [enter] to quit.\n" );
if( getchar() == 'q' )
break;
Pa_RefreshDeviceList();
}
Pa_Terminate();
return 0;
}

View file

@ -59,9 +59,9 @@
typedef struct
{
short sine[TABLE_SIZE];
int32_t phases[MAX_CHANNELS];
int32_t numChannels;
int32_t sampsToGo;
long phases[MAX_CHANNELS];
long numChannels;
long sampsToGo;
}
paTestData;
@ -171,7 +171,7 @@ int main(int argc, char **args)
goto error;
}
inputParameters.channelCount = 2;
inputParameters.channelCount = 1;
inputParameters.sampleFormat = paInt16;
deviceInfo = Pa_GetDeviceInfo( inputParameters.device );
if( deviceInfo == NULL )
@ -227,10 +227,21 @@ int main(int argc, char **args)
printf("Pa_IsStreamActive(outputStream) = %d\n", Pa_IsStreamActive(outputStream));
} while( Pa_IsStreamActive(inputStream) && Pa_IsStreamActive(outputStream) );
#if 1
err = Pa_StopStream( inputStream );
if( err != paNoError ) goto error;
printf("Input stream stopped OK.\n");
err = Pa_StopStream( outputStream );
if( err != paNoError ) goto error;
printf("Output stream stopped OK.\n");
#endif
err = Pa_CloseStream( inputStream );
if( err != paNoError ) goto error;
printf("Input stream closed OK.\n");
err = Pa_CloseStream( outputStream );
if( err != paNoError ) goto error;
printf("Output stream closed OK.\n");
Pa_Terminate();
return paNoError;
error:

View file

@ -0,0 +1,273 @@
/** @file patest_unplug.c
@ingroup test_src
@brief Debug a crash involving unplugging a USB device.
@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.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <math.h>
#include "portaudio.h"
#define NUM_SECONDS (8)
#define SAMPLE_RATE (44100)
#ifndef M_PI
#define M_PI (3.14159265)
#endif
#define TABLE_SIZE (200)
#define FRAMES_PER_BUFFER (64)
#define MAX_CHANNELS (8)
#define INPUT_CHANNELS (1) /* 1 so it works with headset mic */
#define OUTPUT_CHANNELS (2)
typedef struct
{
short sine[TABLE_SIZE];
long phases[MAX_CHANNELS];
long numChannels;
}
paTestData;
static void generateSine( short *out,
unsigned long framesPerBuffer,
paTestData *data )
{
unsigned int i;
int channelIndex;
for( i=0; i<framesPerBuffer; i++ )
{
for (channelIndex = 0; channelIndex < data->numChannels; channelIndex++)
{
int phase = data->phases[channelIndex];
*out++ = data->sine[phase];
phase += channelIndex + 2;
if( phase >= TABLE_SIZE ) phase -= TABLE_SIZE;
data->phases[channelIndex] = phase;
}
}
}
/*******************************************************************/
int main(int argc, char **args);
int main(int argc, char **args)
{
PaStreamParameters inputParameters;
PaStreamParameters outputParameters;
PaStream *inputStream;
PaStream *outputStream;
const PaDeviceInfo *deviceInfo;
PaError err;
paTestData data;
int i;
int totalSamps;
int inputDevice = -1;
int outputDevice = -1;
int sampsToGo;
short inputBuffer[INPUT_CHANNELS * FRAMES_PER_BUFFER];
short outputBuffer[OUTPUT_CHANNELS * FRAMES_PER_BUFFER];
printf("Test unplugging a USB device.\n");
if( argc > 1 ) {
inputDevice = outputDevice = atoi( args[1] );
printf("Using device number %d.\n\n", inputDevice );
} else {
printf("Using default device.\n\n" );
}
memset(&data, 0, sizeof(data));
/* 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.numChannels = OUTPUT_CHANNELS;
sampsToGo = totalSamps = NUM_SECONDS * SAMPLE_RATE; /* Play for a few seconds. */
err = Pa_Initialize();
if( err != paNoError ) goto error;
if( inputDevice == -1 )
inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
else
inputParameters.device = inputDevice ;
if (inputParameters.device == paNoDevice) {
fprintf(stderr,"Error: No default input device.\n");
goto error;
}
if( outputDevice == -1 )
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
else
outputParameters.device = outputDevice ;
if (outputParameters.device == paNoDevice) {
fprintf(stderr,"Error: No default output device.\n");
goto error;
}
inputParameters.channelCount = INPUT_CHANNELS;
inputParameters.sampleFormat = paInt16;
deviceInfo = Pa_GetDeviceInfo( inputParameters.device );
if( deviceInfo == NULL )
{
fprintf( stderr, "No matching input device.\n" );
goto error;
}
inputParameters.suggestedLatency = deviceInfo->defaultLowInputLatency;
inputParameters.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(
&inputStream,
&inputParameters,
NULL,
SAMPLE_RATE,
FRAMES_PER_BUFFER,
0,
NULL,
&data );
if( err != paNoError ) goto error;
outputParameters.channelCount = OUTPUT_CHANNELS;
outputParameters.sampleFormat = paInt16;
deviceInfo = Pa_GetDeviceInfo( outputParameters.device );
if( deviceInfo == NULL )
{
fprintf( stderr, "No matching output device.\n" );
goto error;
}
outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(
&outputStream,
NULL,
&outputParameters,
SAMPLE_RATE,
FRAMES_PER_BUFFER,
(paClipOff | paDitherOff),
NULL,
&data );
if( err != paNoError ) goto error;
err = Pa_StartStream( inputStream );
if( err != paNoError ) goto error;
err = Pa_StartStream( outputStream );
if( err != paNoError ) goto error;
printf("When you hear sound, unplug the USB device.\n");
do
{
signed long available;
available = Pa_GetStreamReadAvailable(inputStream);
while( available > 0 ) {
if( available > FRAMES_PER_BUFFER )
available = FRAMES_PER_BUFFER;
err = Pa_ReadStream( inputStream, inputBuffer, available ); /* reading <= available means don't block */
if( err != paNoError ) goto done; /* Move on to stopping stream */
sampsToGo -= available;
available = Pa_GetStreamReadAvailable(inputStream);
}
available = Pa_GetStreamWriteAvailable(outputStream);
while( available > 0 ) {
if( available > FRAMES_PER_BUFFER )
available = FRAMES_PER_BUFFER;
generateSine( outputBuffer, available, &data );
err = Pa_WriteStream( outputStream, outputBuffer, available ); /* reading <= available means don't block */
if( err != paNoError ) goto done; /* Move on to stopping stream */
available = Pa_GetStreamWriteAvailable(outputStream);
}
Pa_Sleep(1);
printf("Frames remaining = %d\n", sampsToGo);
printf("Pa_IsStreamActive(inputStream) = %d\n", Pa_IsStreamActive(inputStream));
printf("Pa_IsStreamActive(outputStream) = %d\n", Pa_IsStreamActive(outputStream));
} while( Pa_IsStreamActive(inputStream) && Pa_IsStreamActive(outputStream) && sampsToGo > 0 );
done:
#if 1
printf("Stopping input stream...\n");
err = Pa_StopStream( inputStream );
if( err != paNoError ) goto error;
printf("Input stream stopped OK.\n");
printf("Stopping output stream...\n");
err = Pa_StopStream( outputStream );
if( err != paNoError ) goto error;
printf("Output stream stopped OK.\n");
#endif
#if 0
err = Pa_AbortStream( inputStream );
if( err != paNoError ) goto error;
printf("Input stream stopped OK.\n");
err = Pa_AbortStream( outputStream );
if( err != paNoError ) goto error;
printf("Output stream stopped OK.\n");
#endif
err = Pa_CloseStream( inputStream );
if( err != paNoError ) goto error;
printf("Input stream closed OK.\n");
err = Pa_CloseStream( outputStream );
if( err != paNoError ) goto error;
printf("Output stream closed OK.\n");
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 ) );
fprintf( stderr, "Host Error message: %s\n", Pa_GetLastHostErrorInfo()->errorText );
return err;
}