SimpleAudio/Plug-In/SA_PlugIn.cpp

/*
     File: SA_PlugIn.cpp 
 Abstract:  Part of SimpleAudioDriver Plug-In Example  
  Version: 1.0.1 
  
 Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple 
 Inc. ("Apple") in consideration of your agreement to the following 
 terms, and your use, installation, modification or redistribution of 
 this Apple software constitutes acceptance of these terms.  If you do 
 not agree with these terms, please do not use, install, modify or 
 redistribute this Apple software. 
  
 In consideration of your agreement to abide by the following terms, and 
 subject to these terms, Apple grants you a personal, non-exclusive 
 license, under Apple's copyrights in this original Apple software (the 
 "Apple Software"), to use, reproduce, modify and redistribute the Apple 
 Software, with or without modifications, in source and/or binary forms; 
 provided that if you redistribute the Apple Software in its entirety and 
 without modifications, you must retain this notice and the following 
 text and disclaimers in all such redistributions of the Apple Software. 
 Neither the name, trademarks, service marks or logos of Apple Inc. may 
 be used to endorse or promote products derived from the Apple Software 
 without specific prior written permission from Apple.  Except as 
 expressly stated in this notice, no other rights or licenses, express or 
 implied, are granted by Apple herein, including but not limited to any 
 patent rights that may be infringed by your derivative works or by other 
 works in which the Apple Software may be incorporated. 
  
 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE 
 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 
 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 
 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 
 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 
  
 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 
 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 
 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 
 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 
 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE. 
  
 Copyright (C) 2013 Apple Inc. All Rights Reserved. 
  
*/
/*==================================================================================================
    SA_PlugIn.cpp
==================================================================================================*/
 
//==================================================================================================
//  Includes
//==================================================================================================
 
//  Self Include
#include "SA_PlugIn.h"
 
//  Local Includes
#include "SA_Device.h"
#include "SA_IOKit.h"
#include "SimpleAudioDriverTypes.h"
 
//  PublicUtility Includes
#include "CADebugMacros.h"
#include "CAException.h"
 
//  System Includes
#include <IOKit/IOMessage.h>
 
//==================================================================================================
//  SA_PlugIn
//==================================================================================================
 
SA_PlugIn&  SA_PlugIn::GetInstance()
{
    pthread_once(&sStaticInitializer, StaticInitializer);
    return *sInstance;
}
 
SA_PlugIn::SA_PlugIn()
:
    SA_Object(kAudioObjectPlugInObject, kAudioPlugInClassID, kAudioObjectClassID, 0),
    mDeviceInfoList(),
    mIOKitNotificationPort(NULL),
    mMatchingNotification(IO_OBJECT_NULL),
    mDispatchQueue("SA_PlugIn"),
    mMutex("SA_PlugIn")
{
}
 
SA_PlugIn::~SA_PlugIn()
{
}
 
void    SA_PlugIn::Activate()
{
    _StartDeviceListNotifications();
    SA_Object::Activate();
}
 
void    SA_PlugIn::Deactivate()
{
    CAMutex::Locker theLocker(mMutex);
    SA_Object::Deactivate();
    _StopDeviceListNotifications();
    _RemoveAllDevices();
}
 
void    SA_PlugIn::StaticInitializer()
{
    try
    {
        sInstance = new SA_PlugIn;
        SA_ObjectMap::MapObject(kAudioObjectPlugInObject, sInstance);
        sInstance->Activate();
    }
    catch(...)
    {
        DebugMsg("SA_PlugIn::StaticInitializer: failed to create the plug-in");
        delete sInstance;
        sInstance = NULL;
    }
}
 
bool    SA_PlugIn::HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const
{
    bool theAnswer = false;
    switch(inAddress.mSelector)
    {
        case kAudioObjectPropertyManufacturer:
        case kAudioPlugInPropertyDeviceList:
        case kAudioPlugInPropertyTranslateUIDToDevice:
        case kAudioPlugInPropertyResourceBundle:
            theAnswer = true;
            break;
        
        default:
            theAnswer = SA_Object::HasProperty(inObjectID, inClientPID, inAddress);
    };
    return theAnswer;
}
 
bool    SA_PlugIn::IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const
{
    bool theAnswer = false;
    switch(inAddress.mSelector)
    {
        case kAudioObjectPropertyManufacturer:
        case kAudioPlugInPropertyDeviceList:
        case kAudioPlugInPropertyTranslateUIDToDevice:
        case kAudioPlugInPropertyResourceBundle:
            theAnswer = false;
            break;
        
        default:
            theAnswer = SA_Object::IsPropertySettable(inObjectID, inClientPID, inAddress);
    };
    return theAnswer;
}
 
UInt32  SA_PlugIn::GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const
{
    UInt32 theAnswer = 0;
    switch(inAddress.mSelector)
    {
        case kAudioObjectPropertyManufacturer:
            theAnswer = sizeof(CFStringRef);
            break;
            
        case kAudioObjectPropertyOwnedObjects:
        case kAudioPlugInPropertyDeviceList:
            {
                CAMutex::Locker theLocker(mMutex);
                theAnswer = static_cast<UInt32>(mDeviceInfoList.size() * sizeof(AudioObjectID));
            }
            break;
            
        case kAudioPlugInPropertyTranslateUIDToDevice:
            theAnswer = sizeof(AudioObjectID);
            break;
            
        case kAudioPlugInPropertyResourceBundle:
            theAnswer = sizeof(CFStringRef);
            break;
        
        default:
            theAnswer = SA_Object::GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData);
    };
    return theAnswer;
}
 
void    SA_PlugIn::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const
{
    switch(inAddress.mSelector)
    {
        case kAudioObjectPropertyManufacturer:
            //  This is the human readable name of the maker of the plug-in.
            ThrowIf(inDataSize < sizeof(CFStringRef), CAException(kAudioHardwareBadPropertySizeError), "SA_PlugIn::GetPropertyData: not enough space for the return value of kAudioObjectPropertyManufacturer");
            *reinterpret_cast<CFStringRef*>(outData) = CFSTR("Apple Inc.");
            outDataSize = sizeof(CFStringRef);
            break;
            
        case kAudioObjectPropertyOwnedObjects:
        case kAudioPlugInPropertyDeviceList:
            //  The plug-in object only owns devices, so the the owned object list and the device
            //  list are actually the same thing. We need to be holding the mutex to access the
            //  device info list.
            {
                CAMutex::Locker theLocker(mMutex);
                
                //  Calculate the number of items that have been requested. Note that this
                //  number is allowed to be smaller than the actual size of the list. In such
                //  case, only that number of items will be returned
                UInt32 theNumberItemsToFetch = static_cast<UInt32>(std::min(inDataSize / sizeof(AudioObjectID), mDeviceInfoList.size()));
                
                //  go through the device list and copy out the devices' object IDs
                AudioObjectID* theReturnedDeviceList = reinterpret_cast<AudioObjectID*>(outData);
                for(UInt32 theDeviceIndex = 0; theDeviceIndex < theNumberItemsToFetch; ++theDeviceIndex)
                {
                    theReturnedDeviceList[theDeviceIndex] = mDeviceInfoList[theDeviceIndex].mDeviceObjectID;
                }
                
                //  say how much we returned
                outDataSize = theNumberItemsToFetch * sizeof(AudioObjectID);
            }
            break;
            
        case kAudioPlugInPropertyTranslateUIDToDevice:
            //  This property translates the UID passed in the qualifier as a CFString into the
            //  AudioObjectID for the device the UID refers to or kAudioObjectUnknown if no device
            //  has the UID.
            ThrowIf(inQualifierDataSize < sizeof(CFStringRef), CAException(kAudioHardwareBadPropertySizeError), "SA_PlugIn::GetPropertyData: the qualifier size is too small for kAudioPlugInPropertyTranslateUIDToDevice");
            ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), "SA_PlugIn::GetPropertyData: not enough space for the return value of kAudioPlugInPropertyTranslateUIDToDevice");
            outDataSize = sizeof(AudioObjectID);
            break;
            
        case kAudioPlugInPropertyResourceBundle:
            //  The resource bundle is a path relative to the path of the plug-in's bundle.
            //  To specify that the plug-in bundle itself should be used, we just return the
            //  empty string.
            ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), "SA_GetPlugInPropertyData: not enough space for the return value of kAudioPlugInPropertyResourceBundle");
            *reinterpret_cast<CFStringRef*>(outData) = CFSTR("");
            outDataSize = sizeof(CFStringRef);
            break;
        
        default:
            SA_Object::GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData);
            break;
    };
}
 
void    SA_PlugIn::SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)
{
    switch(inAddress.mSelector)
    {
        default:
            SA_Object::SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData);
            break;
    };
}
 
void    SA_PlugIn::_StartDeviceListNotifications()
{
    if((mIOKitNotificationPort == NULL) && (mMatchingNotification == IO_OBJECT_NULL))
    {
        try
        {
            //  get the IOKit master port
            mach_port_t theMasterPort = MACH_PORT_NULL;
            kern_return_t theKernelError = IOMasterPort(bootstrap_port, &theMasterPort);
            ThrowIfKernelError(theKernelError, CAException(theKernelError), "SA_PlugIn::_StartDeviceListNotifications: IOMasterPort failed");
            
            //  create an IOKit notification port
            mIOKitNotificationPort = IONotificationPortCreate(theMasterPort);
            ThrowIfNULL(mIOKitNotificationPort, CAException(kAudioHardwareUnspecifiedError), "SA_PlugIn::_StartDeviceListNotifications: IONotificationPortCreate failed");
 
            //  tell the port to use our command gate's dispatch queue
            IONotificationPortSetDispatchQueue(mIOKitNotificationPort, mDispatchQueue.GetDispatchQueue());
 
            //  create a matching dictionary for 
            CFDictionaryRef theMatchingDictionary = IOServiceMatching(kSimpleAudioDriverClassName);
            ThrowIfNULL(theMatchingDictionary, CAException(kAudioHardwareUnspecifiedError), "SA_PlugIn::_StartDeviceListNotifications: IOServiceMatching failed");
            
            //  sign up for notification for when new IOAudioEngines show up (this consumes a ref on theMatchingDictionary)
            theKernelError = IOServiceAddMatchingNotification(mIOKitNotificationPort, kIOFirstPublishNotification, theMatchingDictionary, IOServiceMatchingHandler, GetObjectIDAsPtr(), &mMatchingNotification);
            ThrowIfKernelError(theKernelError, CAException(theKernelError), "SA_PlugIn::_StartDeviceListNotifications: IOServiceAddMatchingNotification failed");
            
            //  create the devices indicated by the returned io_iterator_t
            IOServiceMatchingHandler(GetObjectIDAsPtr(), mMatchingNotification);
        }
        catch(...)
        {
            //  release the IOKit notification object
            if(mMatchingNotification != IO_OBJECT_NULL)
            {
                IOObjectRelease(mMatchingNotification);
                mMatchingNotification = IO_OBJECT_NULL;
            }
            
            //  release the IOKit notification port
            if(mIOKitNotificationPort != NULL)
            {
                IONotificationPortDestroy(mIOKitNotificationPort);
                mIOKitNotificationPort = NULL;
            }
            
            //  rethrow the exception
            throw;
        }
    }
}
 
void    SA_PlugIn::_StopDeviceListNotifications()
{
    //  release the IOKit notification object
    if(mMatchingNotification != IO_OBJECT_NULL)
    {
        IOObjectRelease(mMatchingNotification);
        mMatchingNotification = IO_OBJECT_NULL;
    }
    
    //  release the IOKit notification port
    if(mIOKitNotificationPort)
    {
        IONotificationPortDestroy(mIOKitNotificationPort);
        mIOKitNotificationPort = NULL;
    }
}
 
void    SA_PlugIn::AddDevice(SA_Device* inDevice)
{
    CAMutex::Locker theLocker(mMutex);
    _AddDevice(inDevice);
}
 
void    SA_PlugIn::RemoveDevice(SA_Device* inDevice)
{
    CAMutex::Locker theLocker(mMutex);
    _RemoveDevice(inDevice);
}
 
SA_Device*  SA_PlugIn::CopyDeviceByIOObject(io_object_t inIOObject)
{
    CAMutex::Locker theLocker(mMutex);
    return _CopyDeviceByIOObject(inIOObject);
}
 
void    SA_PlugIn::_AddDevice(SA_Device* inDevice)
{
    if(inDevice != NULL)
    {
        //  Initialize an DeviceInfo to describe the new device
        DeviceInfo theDeviceInfo(inDevice->GetObjectID());
        
        //  Set up the interest notification
        kern_return_t theKernelError = IOServiceAddInterestNotification(mIOKitNotificationPort, inDevice->GetIOKitObject(), kIOGeneralInterest, IOServiceInterestHandler, GetObjectIDAsPtr(), &theDeviceInfo.mInterestNotification);
        ThrowIfKernelError(theKernelError, CAException(theKernelError), "SA_PlugIn::_AddDevice: Cannot add an interest callback.");
 
        //  tell the port to use our command gate's dispatch queue
        IONotificationPortSetDispatchQueue(mIOKitNotificationPort, mDispatchQueue.GetDispatchQueue());
        
        //  put the device info in the list
        mDeviceInfoList.push_back(theDeviceInfo);
    }
}
 
void    SA_PlugIn::_RemoveDevice(SA_Device* inDevice)
{
    //  find it in the device list and grab an iterator for it
    if(inDevice != NULL)
    {
        bool wasFound = false;
        DeviceInfoList::iterator theDeviceIterator = mDeviceInfoList.begin();
        while(!wasFound && (theDeviceIterator != mDeviceInfoList.end()))
        {
            if(inDevice->GetObjectID() == theDeviceIterator->mDeviceObjectID)
            {
                wasFound = true;
                
                //  clean up the interest notification
                IOObjectRelease(theDeviceIterator->mInterestNotification);
                theDeviceIterator->mInterestNotification = IO_OBJECT_NULL;
                
                //  remove the device from the list
                theDeviceIterator->mDeviceObjectID = 0;
                mDeviceInfoList.erase(theDeviceIterator);
            }
            else
            {
                ++theDeviceIterator;
            }
        }
    }
}
 
void    SA_PlugIn::_RemoveAllDevices()
{
    //  spin through the device list
    for(DeviceInfoList::iterator theDeviceIterator = mDeviceInfoList.begin(); theDeviceIterator != mDeviceInfoList.end(); ++theDeviceIterator)
    {
        //  clean up the interest notification
        IOObjectRelease(theDeviceIterator->mInterestNotification);
        theDeviceIterator->mInterestNotification = IO_OBJECT_NULL;
        
        //  remove the object from the list
        AudioObjectID theDeadDeviceObjectID = theDeviceIterator->mDeviceObjectID;
        theDeviceIterator->mDeviceObjectID = 0;
        
        //  asynchronously get rid of the device since we are holding the plug-in's state lock
        CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{
                                                                    CATry;
                                                                    //  resolve the device ID to an object
                                                                    SA_ObjectReleaser<SA_Device> theDeadDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(theDeadDeviceObjectID));
                                                                    if(theDeadDevice.IsValid())
                                                                    {
                                                                        //  deactivate the device
                                                                        theDeadDevice->Deactivate();
                                                                        
                                                                        //  and release it
                                                                        SA_ObjectMap::ReleaseObject(theDeadDevice);
                                                                    }
                                                                    CACatch;
                                                                });
    }
}
 
SA_Device*  SA_PlugIn::_CopyDeviceByIOObject(io_object_t inIOObject)
{
    //  Because of the vagaries of IOKit notifications, it is quite often the case that the actual
    //  value of the io_object_t to look up will not actually match any of the io_object_t's that
    //  we've seen before. So to do the matching here, we look up an IORegistry property that is
    //  different for each device, like the device UID property, and see if that matches anything
    //  we know about.
 
    SA_Device* theAnswer = NULL;
    CACFString theDeviceUIDToFind(SA_Device::HW_CopyDeviceUID(inIOObject));
    if(theDeviceUIDToFind.IsValid())
    {
        DeviceInfoList::iterator theDeviceIterator = mDeviceInfoList.begin();
        while((theAnswer == NULL) && (theDeviceIterator != mDeviceInfoList.end()))
        {
            SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(theDeviceIterator->mDeviceObjectID));
            if(theDevice.IsValid())
            {
                CACFString theDeviceUID(theDevice->CopyDeviceUID());
                if(theDeviceUIDToFind == theDeviceUID)
                {
                    theAnswer = theDevice;
                    SA_ObjectMap::RetainObject(theAnswer);
                }
            }
            ++theDeviceIterator;
        }
    }
    return theAnswer;
}
 
void    SA_PlugIn::IOServiceMatchingHandler(void* inContext, io_iterator_t inIterator)
{
    bool deviceWasAdded = false;
    SA_ObjectReleaser<SA_PlugIn> thePlugIn(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_PlugIn>(static_cast<AudioObjectID>(reinterpret_cast<uintptr_t>(inContext))));
    ThrowIf(!thePlugIn.IsValid(), CAException(kAudioHardwareIllegalOperationError), "SA_PlugIn::IOServiceMatchingHandler: no plug-in object");
    
    SA_IOKitIterator theIterator(inIterator, false);
    SA_IOKitObject theService(theIterator.Next());
    while(theService.IsValid())
    {
        if(theService.ConformsTo(kSimpleAudioDriverClassName))
        {
            //  Note that we catch all exceptions here so that we can finish processing the items in the notification
            SA_Device* theNewDevice = NULL;
            try
            {
                //  make the new device object
                AudioObjectID theNewDeviceObjectID = SA_ObjectMap::GetNextObjectID();
                theNewDevice = new SA_Device(theNewDeviceObjectID, theService.CopyObject());
                
                //  add it to the object map
                SA_ObjectMap::MapObject(theNewDeviceObjectID, theNewDevice);
                
                //  add it to the device list
                thePlugIn->AddDevice(theNewDevice);
                
                //  activate the device
                theNewDevice->Activate();
                
                deviceWasAdded = true;
            }
            catch(...)
            {
                thePlugIn->RemoveDevice(theNewDevice);
                SA_ObjectMap::ReleaseObject(theNewDevice);
            }
        }
 
        theService = theIterator.Next();
    }
    
    if(deviceWasAdded)
    {
        //  this will change the owned object list and the device list
        AudioObjectPropertyAddress theChangedProperties[] { { kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster },
                                                            { kAudioPlugInPropertyDeviceList, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster } };
        Host_PropertiesChanged(thePlugIn->GetObjectID(), 2, theChangedProperties);
    }
}
 
void    SA_PlugIn::IOServiceInterestHandler(void* inContext, io_service_t inService, natural_t inMessageType, void*)
{
    if((inService != IO_OBJECT_NULL) && (inService != MACH_PORT_DEAD) && (inMessageType == kIOMessageServiceIsTerminated))
    {
        CATry;
        
        //  get the plug-in object
        SA_ObjectReleaser<SA_PlugIn> thePlugIn(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_PlugIn>(static_cast<AudioObjectID>(reinterpret_cast<uintptr_t>(inContext))));
        ThrowIf(!thePlugIn.IsValid(), CAException(kAudioHardwareIllegalOperationError), "SA_PlugIn::IOServiceInterestHandler: no plug-in object");
        
        //  get the dead device
        SA_ObjectReleaser<SA_Device> theDeadDevice(thePlugIn->CopyDeviceByIOObject(inService));
        if(theDeadDevice.IsValid())
        {
            //  remove it from our list
            thePlugIn->RemoveDevice(theDeadDevice);
            
            //  deactivate the device
            theDeadDevice->Deactivate();
            
            //  release it
            SA_ObjectMap::ReleaseObject(theDeadDevice);
            
            //  this will change the owned object list and the device list
            AudioObjectPropertyAddress theChangedProperties[] { { kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster },
                                                                { kAudioPlugInPropertyDeviceList, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster } };
            Host_PropertiesChanged(thePlugIn->GetObjectID(), 2, theChangedProperties);
        }
    
        CACatch;
    }
}
 
pthread_once_t              SA_PlugIn::sStaticInitializer = PTHREAD_ONCE_INIT;
SA_PlugIn*                  SA_PlugIn::sInstance = NULL;
AudioServerPlugInHostRef    SA_PlugIn::sHost = NULL;
 
#pragma mark COM Prototypes
 
//  Entry points for the COM methods
extern "C" void*    SA_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID);
static HRESULT      SA_QueryInterface(void* inDriver, REFIID inUUID, LPVOID* outInterface);
static ULONG        SA_AddRef(void* inDriver);
static ULONG        SA_Release(void* inDriver);
static OSStatus     SA_Initialize(AudioServerPlugInDriverRef inDriver, AudioServerPlugInHostRef inHost);
static OSStatus     SA_CreateDevice(AudioServerPlugInDriverRef inDriver, CFDictionaryRef inDescription, const AudioServerPlugInClientInfo* inClientInfo, AudioObjectID* outDeviceObjectID);
static OSStatus     SA_DestroyDevice(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID);
static OSStatus     SA_AddDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo);
static OSStatus     SA_RemoveDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo);
static OSStatus     SA_PerformDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo);
static OSStatus     SA_AbortDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo);
static Boolean      SA_HasProperty(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress);
static OSStatus     SA_IsPropertySettable(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, Boolean* outIsSettable);
static OSStatus     SA_GetPropertyDataSize(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize);
static OSStatus     SA_GetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32* outDataSize, void* outData);
static OSStatus     SA_SetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData);
static OSStatus     SA_StartIO(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID);
static OSStatus     SA_StopIO(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID);
static OSStatus     SA_GetZeroTimeStamp(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, Float64* outSampleTime, UInt64* outHostTime, UInt64* outSeed);
static OSStatus     SA_WillDoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, Boolean* outWillDo, Boolean* outWillDoInPlace);
static OSStatus     SA_BeginIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo);
static OSStatus     SA_DoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, AudioObjectID inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer);
static OSStatus     SA_EndIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo);
 
#pragma mark The COM Interface
 
static AudioServerPlugInDriverInterface gAudioServerPlugInDriverInterface =
{
    NULL,
    SA_QueryInterface,
    SA_AddRef,
    SA_Release,
    SA_Initialize,
    SA_CreateDevice,
    SA_DestroyDevice,
    SA_AddDeviceClient,
    SA_RemoveDeviceClient,
    SA_PerformDeviceConfigurationChange,
    SA_AbortDeviceConfigurationChange,
    SA_HasProperty,
    SA_IsPropertySettable,
    SA_GetPropertyDataSize,
    SA_GetPropertyData,
    SA_SetPropertyData,
    SA_StartIO,
    SA_StopIO,
    SA_GetZeroTimeStamp,
    SA_WillDoIOOperation,
    SA_BeginIOOperation,
    SA_DoIOOperation,
    SA_EndIOOperation
};
static AudioServerPlugInDriverInterface*    gAudioServerPlugInDriverInterfacePtr    = &gAudioServerPlugInDriverInterface;
static AudioServerPlugInDriverRef           gAudioServerPlugInDriverRef             = &gAudioServerPlugInDriverInterfacePtr;
static UInt32                               gAudioServerPlugInDriverRefCount        = 1;
 
#pragma mark Factory
 
extern "C"
void*   SA_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID)
{
    //  This is the CFPlugIn factory function. Its job is to create the implementation for the given
    //  type provided that the type is supported. Because this driver is simple and all its
    //  initialization is handled via static iniitalization when the bundle is loaded, all that
    //  needs to be done is to return the AudioServerPlugInDriverRef that points to the driver's
    //  interface. A more complicated driver would create any base line objects it needs to satisfy
    //  the IUnknown methods that are used to discover that actual interface to talk to the driver.
    //  The majority of the driver's initilization should be handled in the Initialize() method of
    //  the driver's AudioServerPlugInDriverInterface.
    
    #pragma unused(inAllocator)
    void* theAnswer = NULL;
    if(CFEqual(inRequestedTypeUUID, kAudioServerPlugInTypeUUID))
    {
        theAnswer = gAudioServerPlugInDriverRef;
        SA_PlugIn::GetInstance();
    }
    return theAnswer;
}
 
#pragma mark Inheritence
 
static HRESULT  SA_QueryInterface(void* inDriver, REFIID inUUID, LPVOID* outInterface)
{
    //  This function is called by the HAL to get the interface to talk to the plug-in through.
    //  AudioServerPlugIns are required to support the IUnknown interface and the
    //  AudioServerPlugInDriverInterface. As it happens, all interfaces must also provide the
    //  IUnknown interface, so we can always just return the single interface we made with
    //  gAudioServerPlugInDriverInterfacePtr regardless of which one is asked for.
 
    //  declare the local variables
    HRESULT theAnswer = 0;
    
    try
    {
        //  validate the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_QueryInterface: bad driver reference");
        ThrowIfNULL(outInterface, CAException(kAudioHardwareIllegalOperationError), "SA_QueryInterface: no place to store the returned interface");
 
        //  make a CFUUIDRef from inUUID
        CACFUUID theRequestedUUID(CFUUIDCreateFromUUIDBytes(NULL, inUUID));
        ThrowIf(!theRequestedUUID.IsValid(), CAException(kAudioHardwareIllegalOperationError), "SA_QueryInterface: failed to create the CFUUIDRef");
 
        //  AudioServerPlugIns only support two interfaces, IUnknown (which has to be supported by all
        //  CFPlugIns and AudioServerPlugInDriverInterface (which is the actual interface the HAL will
        //  use).
        ThrowIf(!CFEqual(theRequestedUUID.GetCFObject(), IUnknownUUID) && !CFEqual(theRequestedUUID.GetCFObject(), kAudioServerPlugInDriverInterfaceUUID), CAException(E_NOINTERFACE), "SA_QueryInterface: requested interface is unsupported");
        ThrowIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, CAException(E_NOINTERFACE), "SA_QueryInterface: the ref count is maxxed out");
        
        //  do the work
        ++gAudioServerPlugInDriverRefCount;
        *outInterface = gAudioServerPlugInDriverRef;
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
        
    return theAnswer;
}
 
static ULONG    SA_AddRef(void* inDriver)
{
    //  This call returns the resulting reference count after the increment.
    
    //  declare the local variables
    ULONG theAnswer = 0;
    
    //  check the arguments
    FailIf(inDriver != gAudioServerPlugInDriverRef, Done, "SA_AddRef: bad driver reference");
    FailIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, Done, "SA_AddRef: out of references");
 
    //  increment the refcount
    ++gAudioServerPlugInDriverRefCount;
    theAnswer = gAudioServerPlugInDriverRefCount;
 
Done:
    return theAnswer;
}
 
static ULONG    SA_Release(void* inDriver)
{
    //  This call returns the resulting reference count after the decrement.
 
    //  declare the local variables
    ULONG theAnswer = 0;
    
    //  check the arguments
    FailIf(inDriver != gAudioServerPlugInDriverRef, Done, "SA_Release: bad driver reference");
    FailIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, Done, "SA_Release: out of references");
 
    //  decrement the refcount
    //  Note that we don't do anything special if the refcount goes to zero as the HAL
    //  will never fully release a plug-in it opens. We keep managing the refcount so that
    //  the API semantics are correct though.
    --gAudioServerPlugInDriverRefCount;
    theAnswer = gAudioServerPlugInDriverRefCount;
 
Done:
    return theAnswer;
}
 
#pragma mark Basic Operations
 
static OSStatus SA_Initialize(AudioServerPlugInDriverRef inDriver, AudioServerPlugInHostRef inHost)
{
    //  The job of this method is, as the name implies, to get the driver initialized. One specific
    //  thing that needs to be done is to store the AudioServerPlugInHostRef so that it can be used
    //  later. Note that when this call returns, the HAL will scan the various lists the driver
    //  maintains (such as the device list) to get the inital set of objects the driver is
    //  publishing. So, there is no need to notifiy the HAL about any objects created as part of the
    //  execution of this method.
 
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_Initialize: bad driver reference");
        
        //  store the AudioServerPlugInHostRef
        SA_PlugIn::GetInstance().SetHost(inHost);
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
 
    return theAnswer;
}
 
static OSStatus SA_CreateDevice(AudioServerPlugInDriverRef inDriver, CFDictionaryRef inDescription, const AudioServerPlugInClientInfo* inClientInfo, AudioObjectID* outDeviceObjectID)
{
    //  This method is used to tell a driver that implements the Transport Manager semantics to
    //  create an AudioEndpointDevice from a set of AudioEndpoints. Since this driver is not a
    //  Transport Manager, we just return kAudioHardwareUnsupportedOperationError.
    
    #pragma unused(inDriver, inDescription, inClientInfo, outDeviceObjectID)
    
    return kAudioHardwareUnsupportedOperationError;
}
 
static OSStatus SA_DestroyDevice(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID)
{
    //  This method is used to tell a driver that implements the Transport Manager semantics to
    //  destroy an AudioEndpointDevice. Since this driver is not a Transport Manager, we just check
    //  the arguments and return kAudioHardwareUnsupportedOperationError.
    
    #pragma unused(inDriver, inDeviceObjectID)
    
    return kAudioHardwareUnsupportedOperationError;
}
 
static OSStatus SA_AddDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo)
{
    //  This method is used to inform the driver about a new client that is using the given device.
    //  This allows the device to act differently depending on who the client is. This driver does
    //  not need to track the clients using the device, so we just return successfully.
    
    #pragma unused(inDriver, inDeviceObjectID, inClientInfo)
    
    return 0;
}
 
static OSStatus SA_RemoveDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo)
{
    //  This method is used to inform the driver about a client that is no longer using the given
    //  device. This driver does not track clients, so we just return successfully.
    
    #pragma unused(inDriver, inDeviceObjectID, inClientInfo)
    
    return 0;
}
 
static OSStatus SA_PerformDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo)
{
    //  This method is called to tell the device that it can perform the configuation change that it
    //  had requested via a call to the host method, RequestDeviceConfigurationChange(). The
    //  arguments, inChangeAction and inChangeInfo are the same as what was passed to
    //  RequestDeviceConfigurationChange().
    //
    //  The HAL guarantees that IO will be stopped while this method is in progress. The HAL will
    //  also handle figuring out exactly what changed for the non-control related properties. This
    //  means that the only notifications that would need to be sent here would be for either
    //  custom properties the HAL doesn't know about or for controls.
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_PerformDeviceConfigurationChange: bad driver reference");
        
        //  get the device object
        SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(inDeviceObjectID));
        ThrowIf(!theDevice.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_PerformDeviceConfigurationChange: unknown device");
        
        //  tell it to do the work
        theDevice->PerformConfigChange(inChangeAction, inChangeInfo);
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
    
    return theAnswer;
}
 
static OSStatus SA_AbortDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo)
{
    //  This method is called to tell the driver that a request for a config change has been denied.
    //  This provides the driver an opportunity to clean up any state associated with the request.
 
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_PerformDeviceConfigurationChange: bad driver reference");
        
        //  get the device object
        SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(inDeviceObjectID));
        ThrowIf(!theDevice.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_PerformDeviceConfigurationChange: unknown device");
        
        //  tell it to do the work
        theDevice->AbortConfigChange(inChangeAction, inChangeInfo);
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
    
    return theAnswer;
}
 
#pragma mark Property Operations
 
static Boolean  SA_HasProperty(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress)
{
    //  This method returns whether or not the given object has the given property.
    
    //  declare the local variables
    Boolean theAnswer = false;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_HasProperty: bad driver reference");
        ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "SA_HasProperty: no address");
        
        //  get the object
        SA_ObjectReleaser<SA_Object> theObject(SA_ObjectMap::CopyObjectByObjectID(inObjectID));
        ThrowIf(!theObject.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_HasProperty: unknown object");
        
        //  tell it to do the work
        theAnswer = theObject->HasProperty(inObjectID, inClientProcessID, *inAddress);
    }
    catch(const CAException& inException)
    {
        theAnswer = false;
    }
    catch(...)
    {
        theAnswer = false;
    }
 
    return theAnswer;
}
 
static OSStatus SA_IsPropertySettable(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, Boolean* outIsSettable)
{
    //  This method returns whether or not the given property on the object can have its value
    //  changed.
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_IsPropertySettable: bad driver reference");
        ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "SA_IsPropertySettable: no address");
        ThrowIfNULL(outIsSettable, CAException(kAudioHardwareIllegalOperationError), "SA_IsPropertySettable: no place to put the return value");
        
        //  get the object
        SA_ObjectReleaser<SA_Object> theObject(SA_ObjectMap::CopyObjectByObjectID(inObjectID));
        ThrowIf(!theObject.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_IsPropertySettable: unknown object");
        
        //  tell it to do the work
        if(theObject->HasProperty(inObjectID, inClientProcessID, *inAddress))
        {
            *outIsSettable = theObject->IsPropertySettable(inObjectID, inClientProcessID, *inAddress);
        }
        else
        {
            theAnswer = kAudioHardwareUnknownPropertyError;
        }
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
    
    return theAnswer;
}
 
static OSStatus SA_GetPropertyDataSize(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize)
{
    //  This method returns the byte size of the property's data.
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_GetPropertyDataSize: bad driver reference");
        ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "SA_GetPropertyDataSize: no address");
        ThrowIfNULL(outDataSize, CAException(kAudioHardwareIllegalOperationError), "SA_GetPropertyDataSize: no place to put the return value");
        
        //  get the object
        SA_ObjectReleaser<SA_Object> theObject(SA_ObjectMap::CopyObjectByObjectID(inObjectID));
        ThrowIf(!theObject.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_GetPropertyDataSize: unknown object");
        
        //  tell it to do the work
        if(theObject->HasProperty(inObjectID, inClientProcessID, *inAddress))
        {
            *outDataSize = theObject->GetPropertyDataSize(inObjectID, inClientProcessID, *inAddress, inQualifierDataSize, inQualifierData);
        }
        else
        {
            theAnswer = kAudioHardwareUnknownPropertyError;
        }
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
 
    return theAnswer;
}
 
static OSStatus SA_GetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32* outDataSize, void* outData)
{
    //  This method fetches the data for a given property
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_GetPropertyData: bad driver reference");
        ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "SA_GetPropertyData: no address");
        ThrowIfNULL(outDataSize, CAException(kAudioHardwareIllegalOperationError), "SA_GetPropertyData: no place to put the return value size");
        ThrowIfNULL(outData, CAException(kAudioHardwareIllegalOperationError), "SA_GetPropertyData: no place to put the return value");
        
        //  get the object
        SA_ObjectReleaser<SA_Object> theObject(SA_ObjectMap::CopyObjectByObjectID(inObjectID));
        ThrowIf(!theObject.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_GetPropertyData: unknown object");
        
        //  tell it to do the work
        if(theObject->HasProperty(inObjectID, inClientProcessID, *inAddress))
        {
            theObject->GetPropertyData(inObjectID, inClientProcessID, *inAddress, inQualifierDataSize, inQualifierData, inDataSize, *outDataSize, outData);
        }
        else
        {
            theAnswer = kAudioHardwareUnknownPropertyError;
        }
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
 
    return theAnswer;
}
 
static OSStatus SA_SetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)
{
    //  This method changes the value of the given property
 
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_SetPropertyData: bad driver reference");
        ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "SA_SetPropertyData: no address");
        
        //  get the object
        SA_ObjectReleaser<SA_Object> theObject(SA_ObjectMap::CopyObjectByObjectID(inObjectID));
        ThrowIf(!theObject.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_SetPropertyData: unknown object");
        
        //  tell it to do the work
        if(theObject->HasProperty(inObjectID, inClientProcessID, *inAddress))
        {
            if(theObject->IsPropertySettable(inObjectID, inClientProcessID, *inAddress))
            {
                theObject->SetPropertyData(inObjectID, inClientProcessID, *inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData);
            }
            else
            {
                theAnswer = kAudioHardwareUnsupportedOperationError;
            }
        }
        else
        {
            theAnswer = kAudioHardwareUnknownPropertyError;
        }
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
    
    return theAnswer;
}
 
#pragma mark IO Operations
 
static OSStatus SA_StartIO(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID)
{
    //  This call tells the device that IO is starting for the given client. When this routine
    //  returns, the device's clock is running and it is ready to have data read/written. It is
    //  important to note that multiple clients can have IO running on the device at the same time.
    //  So, work only needs to be done when the first client starts. All subsequent starts simply
    //  increment the counter.
    
    #pragma unused(inClientID)
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_StartIO: bad driver reference");
        
        //  get the object
        SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(inDeviceObjectID));
        ThrowIf(!theDevice.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_StartIO: unknown device");
        
        //  tell it to do the work
        theDevice->StartIO();
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
    
    return theAnswer;
}
 
static OSStatus SA_StopIO(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID)
{
    //  This call tells the device that the client has stopped IO. The driver can stop the hardware
    //  once all clients have stopped.
    
    #pragma unused(inClientID)
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_StopIO: bad driver reference");
        
        //  get the object
        SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(inDeviceObjectID));
        ThrowIf(!theDevice.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_StopIO: unknown device");
        
        //  tell it to do the work
        theDevice->StopIO();
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
    
    return theAnswer;
}
 
static OSStatus SA_GetZeroTimeStamp(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, Float64* outSampleTime, UInt64* outHostTime, UInt64* outSeed)
{
    //  This method returns the current zero time stamp for the device. The HAL models the timing of
    //  a device as a series of time stamps that relate the sample time to a host time. The zero
    //  time stamps are spaced such that the sample times are the value of
    //  kAudioDevicePropertyZeroTimeStampPeriod apart. This is often modeled using a ring buffer
    //  where the zero time stamp is updated when wrapping around the ring buffer.
    
    #pragma unused(inClientID)
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_GetZeroTimeStamp: bad driver reference");
        ThrowIfNULL(outSampleTime, CAException(kAudioHardwareIllegalOperationError), "SA_GetZeroTimeStamp: no place to put the sample time");
        ThrowIfNULL(outHostTime, CAException(kAudioHardwareIllegalOperationError), "SA_GetZeroTimeStamp: no place to put the host time");
        ThrowIfNULL(outSeed, CAException(kAudioHardwareIllegalOperationError), "SA_GetZeroTimeStamp: no place to put the seed");
        
        //  get the object
        SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(inDeviceObjectID));
        ThrowIf(!theDevice.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_GetZeroTimeStamp: unknown device");
        
        //  tell it to do the work
        theDevice->GetZeroTimeStamp(*outSampleTime, *outHostTime, *outSeed);
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
    
    return theAnswer;
}
 
static OSStatus SA_WillDoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, Boolean* outWillDo, Boolean* outWillDoInPlace)
{
    //  This method returns whether or not the device will do a given IO operation.
    
    #pragma unused(inClientID)
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_WillDoIOOperation: bad driver reference");
        ThrowIfNULL(outWillDo, CAException(kAudioHardwareIllegalOperationError), "SA_WillDoIOOperation: no place to put the will-do return value");
        ThrowIfNULL(outWillDoInPlace, CAException(kAudioHardwareIllegalOperationError), "SA_WillDoIOOperation: no place to put the in-place return value");
        
        //  get the object
        SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(inDeviceObjectID));
        ThrowIf(!theDevice.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_WillDoIOOperation: unknown device");
        
        //  tell it to do the work
        bool willDo = false;
        bool willDoInPlace = false;
        theDevice->WillDoIOOperation(inOperationID, willDo, willDoInPlace);
        
        //  set the return values
        *outWillDo = willDo;
        *outWillDoInPlace = willDoInPlace;
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
 
    return theAnswer;
}
 
static OSStatus SA_BeginIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo)
{
    //  This is called at the beginning of an IO operation.
    
    #pragma unused(inClientID)
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_BeginIOOperation: bad driver reference");
        ThrowIfNULL(inIOCycleInfo, CAException(kAudioHardwareIllegalOperationError), "SA_BeginIOOperation: no cycle info");
        
        //  get the object
        SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(inDeviceObjectID));
        ThrowIf(!theDevice.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_BeginIOOperation: unknown device");
        
        //  tell it to do the work
        theDevice->BeginIOOperation(inOperationID, inIOBufferFrameSize, *inIOCycleInfo);
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
    
    return theAnswer;
}
 
static OSStatus SA_DoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, AudioObjectID inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer)
{
    //  This is called to actuall perform a given operation. For this device, all we need to do is
    //  clear the buffer for the ReadInput operation.
    
    #pragma unused(inClientID)
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_EndIOOperation: bad driver reference");
        ThrowIfNULL(inIOCycleInfo, CAException(kAudioHardwareIllegalOperationError), "SA_EndIOOperation: no cycle info");
        
        //  get the object
        SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(inDeviceObjectID));
        ThrowIf(!theDevice.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_EndIOOperation: unknown device");
        
        //  tell it to do the work
        theDevice->DoIOOperation(inStreamObjectID, inOperationID, inIOBufferFrameSize, *inIOCycleInfo, ioMainBuffer, ioSecondaryBuffer);
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
 
    return theAnswer;
}
 
static OSStatus SA_EndIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo)
{
    //  This is called at the end of an IO operation.
    
    #pragma unused(inClientID)
    
    //  declare the local variables
    OSStatus theAnswer = 0;
    
    try
    {
        //  check the arguments
        ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "SA_EndIOOperation: bad driver reference");
        ThrowIfNULL(inIOCycleInfo, CAException(kAudioHardwareIllegalOperationError), "SA_EndIOOperation: no cycle info");
        
        //  get the object
        SA_ObjectReleaser<SA_Device> theDevice(SA_ObjectMap::CopyObjectOfClassByObjectID<SA_Device>(inDeviceObjectID));
        ThrowIf(!theDevice.IsValid(), CAException(kAudioHardwareBadObjectError), "SA_EndIOOperation: unknown device");
        
        //  tell it to do the work
        theDevice->EndIOOperation(inOperationID, inIOBufferFrameSize, *inIOCycleInfo);
    }
    catch(const CAException& inException)
    {
        theAnswer = inException.GetError();
    }
    catch(...)
    {
        theAnswer = kAudioHardwareUnspecifiedError;
    }
    
    return theAnswer;
}