// This file is part of Background Music. // // Background Music is free software: you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 2 of the // License, or (at your option) any later version. // // Background Music is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Background Music. If not, see . // // VC_PlugInInterface.cpp // VCDriver // // Copyright © 2016, 2017 Kyle Neideck // Copyright (C) 2013 Apple Inc. All Rights Reserved. // // Based largely on SA_PlugIn.cpp from Apple's SimpleAudioDriver Plug-In sample code. // https://developer.apple.com/library/mac/samplecode/AudioDriverExamples // // System Includes #include // PublicUtility Includes #include "CADebugMacros.h" #include "CAException.h" // Local Includes #include "VC_Types.h" #include "VC_Object.h" #include "VC_PlugIn.h" #include "VC_Device.h" #pragma mark COM Prototypes // Entry points for the COM methods extern "C" void* VC_Create(CFAllocatorRef inAllocator, CFUUIDRef inRequestedTypeUUID); static HRESULT VC_QueryInterface(void* inDriver, REFIID inUUID, LPVOID* outInterface); static ULONG VC_AddRef(void* inDriver); static ULONG VC_Release(void* inDriver); static OSStatus VC_Initialize(AudioServerPlugInDriverRef inDriver, AudioServerPlugInHostRef inHost); static OSStatus VC_CreateDevice(AudioServerPlugInDriverRef inDriver, CFDictionaryRef inDescription, const AudioServerPlugInClientInfo* inClientInfo, AudioObjectID* outDeviceObjectID); static OSStatus VC_DestroyDevice(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID); static OSStatus VC_AddDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo); static OSStatus VC_RemoveDeviceClient(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, const AudioServerPlugInClientInfo* inClientInfo); static OSStatus VC_PerformDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo); static OSStatus VC_AbortDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo); static Boolean VC_HasProperty(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress); static OSStatus VC_IsPropertySettable(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, Boolean* outIsSettable); static OSStatus VC_GetPropertyDataSize(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize); static OSStatus VC_GetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32* outDataSize, void* outData); static OSStatus VC_SetPropertyData(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData); static OSStatus VC_StartIO(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID); static OSStatus VC_StopIO(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID); static OSStatus VC_GetZeroTimeStamp(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, Float64* outSampleTime, UInt64* outHostTime, UInt64* outSeed); static OSStatus VC_WillDoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, Boolean* outWillDo, Boolean* outWillDoInPlace); static OSStatus VC_BeginIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo); static OSStatus VC_DoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, AudioObjectID inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer); static OSStatus VC_EndIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo); #pragma mark The COM Interface static AudioServerPlugInDriverInterface gAudioServerPlugInDriverInterface = { NULL, VC_QueryInterface, VC_AddRef, VC_Release, VC_Initialize, VC_CreateDevice, VC_DestroyDevice, VC_AddDeviceClient, VC_RemoveDeviceClient, VC_PerformDeviceConfigurationChange, VC_AbortDeviceConfigurationChange, VC_HasProperty, VC_IsPropertySettable, VC_GetPropertyDataSize, VC_GetPropertyData, VC_SetPropertyData, VC_StartIO, VC_StopIO, VC_GetZeroTimeStamp, VC_WillDoIOOperation, VC_BeginIOOperation, VC_DoIOOperation, VC_EndIOOperation }; static AudioServerPlugInDriverInterface* gAudioServerPlugInDriverInterfacePtr = &gAudioServerPlugInDriverInterface; static AudioServerPlugInDriverRef gAudioServerPlugInDriverRef = &gAudioServerPlugInDriverInterfacePtr; static UInt32 gAudioServerPlugInDriverRefCount = 1; // TODO: This name is a bit misleading because the devices are actually owned by the plug-in. static VC_Object& VC_LookUpOwnerObject(AudioObjectID inObjectID) { switch(inObjectID) { case kObjectID_PlugIn: return VC_PlugIn::GetInstance(); case kObjectID_Device: case kObjectID_Stream_Input: case kObjectID_Stream_Output: case kObjectID_Volume_Output_Master: case kObjectID_Mute_Output_Master: return VC_Device::GetInstance(); } DebugMsg("VC_LookUpOwnerObject: unknown object"); Throw(CAException(kAudioHardwareBadObjectError)); } static VC_AbstractDevice& VC_LookUpDevice(AudioObjectID inObjectID) { switch(inObjectID) { case kObjectID_Device: return VC_Device::GetInstance(); } DebugMsg("VC_LookUpDevice: unknown device"); Throw(CAException(kAudioHardwareBadDeviceError)); } #pragma mark Factory extern "C" void* VC_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 initialization 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 initialization should be handled in the Initialize() method of // the driver's AudioServerPlugInDriverInterface. #pragma unused(inAllocator) void* theAnswer = NULL; if(CFEqual(inRequestedTypeUUID, kAudioServerPlugInTypeUUID)) { theAnswer = gAudioServerPlugInDriverRef; VC_PlugIn::GetInstance(); } return theAnswer; } #pragma mark Inheritence static HRESULT VC_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. HRESULT theAnswer = 0; CFUUIDRef theRequestedUUID = NULL; try { // validate the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_QueryInterface: bad driver reference"); ThrowIfNULL(outInterface, CAException(kAudioHardwareIllegalOperationError), "VC_QueryInterface: no place to store the returned interface"); // make a CFUUIDRef from inUUID theRequestedUUID = CFUUIDCreateFromUUIDBytes(NULL, inUUID); ThrowIf(theRequestedUUID == NULL, CAException(kAudioHardwareIllegalOperationError), "VC_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, IUnknownUUID) && !CFEqual(theRequestedUUID, kAudioServerPlugInDriverInterfaceUUID), CAException(E_NOINTERFACE), "VC_QueryInterface: requested interface is unsupported"); ThrowIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, CAException(E_NOINTERFACE), "VC_QueryInterface: the ref count is maxxed out"); // do the work ++gAudioServerPlugInDriverRefCount; *outInterface = gAudioServerPlugInDriverRef; } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { theAnswer = kAudioHardwareUnspecifiedError; } if(theRequestedUUID != NULL) { CFRelease(theRequestedUUID); } return theAnswer; } static ULONG VC_AddRef(void* inDriver) { // This call returns the resulting reference count after the increment. ULONG theAnswer = 0; // check the arguments FailIf(inDriver != gAudioServerPlugInDriverRef, Done, "VC_AddRef: bad driver reference"); FailIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, Done, "VC_AddRef: out of references"); // increment the refcount ++gAudioServerPlugInDriverRefCount; theAnswer = gAudioServerPlugInDriverRefCount; Done: return theAnswer; } static ULONG VC_Release(void* inDriver) { // This call returns the resulting reference count after the decrement. ULONG theAnswer = 0; // check the arguments FailIf(inDriver != gAudioServerPlugInDriverRef, Done, "VC_Release: bad driver reference"); FailIf(gAudioServerPlugInDriverRefCount == UINT32_MAX, Done, "VC_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 VC_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 initial 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. OSStatus theAnswer = 0; try { // Check the arguments. ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_Initialize: bad driver reference"); // Store the AudioServerPlugInHostRef. VC_PlugIn::GetInstance().SetHost(inHost); // Init/activate the device. VC_Device::GetInstance(); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_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 VC_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 VC_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. OSStatus theAnswer = 0; try { // Check the arguments. ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_AddDeviceClient: bad driver reference"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadObjectError), "VC_AddDeviceClient: unknown device"); // Inform the device. VC_LookUpDevice(inDeviceObjectID).AddClient(inClientInfo); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_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. OSStatus theAnswer = 0; try { // Check the arguments. ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_RemoveDeviceClient: bad driver reference"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadObjectError), "VC_RemoveDeviceClient: unknown device"); // Inform the device. VC_LookUpDevice(inDeviceObjectID).RemoveClient(inClientInfo); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_PerformDeviceConfigurationChange(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt64 inChangeAction, void* inChangeInfo) { // This method is called to tell the device that it can perform the configuration 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. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_PerformDeviceConfigurationChange: bad driver reference"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadDeviceError), "VC_PerformDeviceConfigurationChange: unknown device"); // tell the device to do the work VC_LookUpDevice(inDeviceObjectID).PerformConfigChange(inChangeAction, inChangeInfo); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_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. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_PerformDeviceConfigurationChange: bad driver reference"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadDeviceError), "VC_PerformDeviceConfigurationChange: unknown device"); // tell the device to do the work VC_LookUpDevice(inDeviceObjectID).AbortConfigChange(inChangeAction, inChangeInfo); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } #pragma mark Property Operations static Boolean VC_HasProperty(AudioServerPlugInDriverRef inDriver, AudioObjectID inObjectID, pid_t inClientProcessID, const AudioObjectPropertyAddress* inAddress) { // This method returns whether or not the given object has the given property. Boolean theAnswer = false; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_HasProperty: bad driver reference"); ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "VC_HasProperty: no address"); theAnswer = VC_LookUpOwnerObject(inObjectID).HasProperty(inObjectID, inClientProcessID, *inAddress); } catch(const CAException& inException) { theAnswer = false; } catch(...) { LogError("VC_PlugInInterface::VC_HasProperty: unknown exception. (object: %u, address: %u)", inObjectID, inAddress ? inAddress->mSelector : 0); theAnswer = false; } return theAnswer; } static OSStatus VC_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. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_IsPropertySettable: bad driver reference"); ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "VC_IsPropertySettable: no address"); ThrowIfNULL(outIsSettable, CAException(kAudioHardwareIllegalOperationError), "VC_IsPropertySettable: no place to put the return value"); VC_Object& theAudioObject = VC_LookUpOwnerObject(inObjectID); if(theAudioObject.HasProperty(inObjectID, inClientProcessID, *inAddress)) { *outIsSettable = theAudioObject.IsPropertySettable(inObjectID, inClientProcessID, *inAddress); } else { theAnswer = kAudioHardwareUnknownPropertyError; } } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { LogError("VC_PlugInInterface::VC_IsPropertySettable: unknown exception. (object: %u, address: %u)", inObjectID, inAddress ? inAddress->mSelector : 0); theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_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. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_GetPropertyDataSize: bad driver reference"); ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "VC_GetPropertyDataSize: no address"); ThrowIfNULL(outDataSize, CAException(kAudioHardwareIllegalOperationError), "VC_GetPropertyDataSize: no place to put the return value"); VC_Object& theAudioObject = VC_LookUpOwnerObject(inObjectID); if(theAudioObject.HasProperty(inObjectID, inClientProcessID, *inAddress)) { *outDataSize = theAudioObject.GetPropertyDataSize(inObjectID, inClientProcessID, *inAddress, inQualifierDataSize, inQualifierData); } else { theAnswer = kAudioHardwareUnknownPropertyError; } } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { LogError("VC_PlugInInterface::VC_GetPropertyDataSize: unknown exception. (object: %u, address: %u)", inObjectID, inAddress ? inAddress->mSelector : 0); theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_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 OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_GetPropertyData: bad driver reference"); ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "VC_GetPropertyData: no address"); ThrowIfNULL(outDataSize, CAException(kAudioHardwareIllegalOperationError), "VC_GetPropertyData: no place to put the return value size"); ThrowIfNULL(outData, CAException(kAudioHardwareIllegalOperationError), "VC_GetPropertyData: no place to put the return value"); VC_Object& theAudioObject = VC_LookUpOwnerObject(inObjectID); if(theAudioObject.HasProperty(inObjectID, inClientProcessID, *inAddress)) { theAudioObject.GetPropertyData(inObjectID, inClientProcessID, *inAddress, inQualifierDataSize, inQualifierData, inDataSize, *outDataSize, outData); } else { theAnswer = kAudioHardwareUnknownPropertyError; } } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { LogError("VC_PlugInInterface::VC_GetPropertyData: unknown exception. (object: %u, address: %u)", inObjectID, inAddress ? inAddress->mSelector : 0); theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_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 OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_SetPropertyData: bad driver reference"); ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "VC_SetPropertyData: no address"); ThrowIfNULL(inData, CAException(kAudioHardwareIllegalOperationError), "VC_SetPropertyData: no data"); VC_Object& theAudioObject = VC_LookUpOwnerObject(inObjectID); if(theAudioObject.HasProperty(inObjectID, inClientProcessID, *inAddress)) { if(theAudioObject.IsPropertySettable(inObjectID, inClientProcessID, *inAddress)) { theAudioObject.SetPropertyData(inObjectID, inClientProcessID, *inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData); } else { theAnswer = kAudioHardwareUnsupportedOperationError; } } else { theAnswer = kAudioHardwareUnknownPropertyError; } } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { LogError("VC_PlugInInterface::VC_SetPropertyData: unknown exception. (object: %u, address: %u)", inObjectID, inAddress ? inAddress->mSelector : 0); theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } #pragma mark IO Operations static OSStatus VC_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. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_StartIO: bad driver reference"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadDeviceError), "VC_StartIO: unknown device"); // tell the device to do the work VC_LookUpDevice(inDeviceObjectID).StartIO(inClientID); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_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. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_StopIO: bad driver reference"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadDeviceError), "VC_StopIO: unknown device"); // tell the device to do the work VC_LookUpDevice(inDeviceObjectID).StopIO(inClientID); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_GetZeroTimeStamp(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, Float64* outSampleTime, UInt64* outHostTime, UInt64* outSeed) { #pragma unused(inClientID) // 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. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_GetZeroTimeStamp: bad driver reference"); ThrowIfNULL(outSampleTime, CAException(kAudioHardwareIllegalOperationError), "VC_GetZeroTimeStamp: no place to put the sample time"); ThrowIfNULL(outHostTime, CAException(kAudioHardwareIllegalOperationError), "VC_GetZeroTimeStamp: no place to put the host time"); ThrowIfNULL(outSeed, CAException(kAudioHardwareIllegalOperationError), "VC_GetZeroTimeStamp: no place to put the seed"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadDeviceError), "VC_GetZeroTimeStamp: unknown device"); // tell the device to do the work VC_LookUpDevice(inDeviceObjectID).GetZeroTimeStamp(*outSampleTime, *outHostTime, *outSeed); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_WillDoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, Boolean* outWillDo, Boolean* outWillDoInPlace) { #pragma unused(inClientID) // This method returns whether or not the device will do a given IO operation. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_WillDoIOOperation: bad driver reference"); ThrowIfNULL(outWillDo, CAException(kAudioHardwareIllegalOperationError), "VC_WillDoIOOperation: no place to put the will-do return value"); ThrowIfNULL(outWillDoInPlace, CAException(kAudioHardwareIllegalOperationError), "VC_WillDoIOOperation: no place to put the in-place return value"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadDeviceError), "VC_WillDoIOOperation: unknown device"); // tell the device to do the work bool willDo = false; bool willDoInPlace = false; VC_LookUpDevice(inDeviceObjectID).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 VC_BeginIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo) { // This is called at the beginning of an IO operation. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_BeginIOOperation: bad driver reference"); ThrowIfNULL(inIOCycleInfo, CAException(kAudioHardwareIllegalOperationError), "VC_BeginIOOperation: no cycle info"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadDeviceError), "VC_BeginIOOperation: unknown device"); // tell the device to do the work VC_LookUpDevice(inDeviceObjectID).BeginIOOperation(inOperationID, inIOBufferFrameSize, *inIOCycleInfo, inClientID); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { DebugMsg("VC_PlugInInterface::VC_BeginIOOperation: unknown exception. (device: %s, operation: %u)", (inDeviceObjectID == kObjectID_Device ? "VCDevice" : "other"), inOperationID); theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_DoIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, AudioObjectID inStreamObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer) { // This is called to actually perform a given IO operation. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_EndIOOperation: bad driver reference"); ThrowIfNULL(inIOCycleInfo, CAException(kAudioHardwareIllegalOperationError), "VC_EndIOOperation: no cycle info"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadDeviceError), "VC_EndIOOperation: unknown device"); // tell the device to do the work VC_LookUpDevice(inDeviceObjectID).DoIOOperation(inStreamObjectID, inClientID, inOperationID, inIOBufferFrameSize, *inIOCycleInfo, ioMainBuffer, ioSecondaryBuffer); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { DebugMsg("VC_PlugInInterface::VC_DoIOOperation: unknown exception. (device: %s, operation: %u)", (inDeviceObjectID == kObjectID_Device ? "VCDevice" : "other"), inOperationID); theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; } static OSStatus VC_EndIOOperation(AudioServerPlugInDriverRef inDriver, AudioObjectID inDeviceObjectID, UInt32 inClientID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo* inIOCycleInfo) { // This is called at the end of an IO operation. OSStatus theAnswer = 0; try { // check the arguments ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "VC_EndIOOperation: bad driver reference"); ThrowIfNULL(inIOCycleInfo, CAException(kAudioHardwareIllegalOperationError), "VC_EndIOOperation: no cycle info"); ThrowIf(inDeviceObjectID != kObjectID_Device, CAException(kAudioHardwareBadDeviceError), "VC_EndIOOperation: unknown device"); // tell the device to do the work VC_LookUpDevice(inDeviceObjectID).EndIOOperation(inOperationID, inIOBufferFrameSize, *inIOCycleInfo, inClientID); } catch(const CAException& inException) { theAnswer = inException.GetError(); } catch(...) { DebugMsg("VC_PlugInInterface::VC_EndIOOperation: unknown exception. (device: %s, operation: %u)", (inDeviceObjectID == kObjectID_Device ? "VCDevice" : "other"), inOperationID); theAnswer = kAudioHardwareUnspecifiedError; } return theAnswer; }