/* |
File: SA_Device.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_Device.cpp |
==================================================================================================*/ |
|
//================================================================================================== |
// Includes |
//================================================================================================== |
|
// Self Include |
#include "SA_Device.h" |
|
// Local Includes |
#include "SA_PlugIn.h" |
#include "SimpleAudioDriverTypes.h" |
|
// PublicUtility Includes |
#include "CADebugMacros.h" |
#include "CADispatchQueue.h" |
#include "CAException.h" |
|
//================================================================================================== |
// SA_Device |
//================================================================================================== |
|
#pragma mark Construction/Destruction |
|
SA_Device::SA_Device(AudioObjectID inObjectID, io_object_t inIOKitObject) |
: |
SA_Object(inObjectID, kAudioDeviceClassID, kAudioObjectClassID, kAudioObjectPlugInObject), |
mStateMutex("Device State"), |
mIOMutex("Device IO"), |
mIOKitObject(inIOKitObject), |
mDeviceUID(HW_CopyDeviceUID(inIOKitObject)), |
mStartCount(0), |
mSampleRateShadow(0), |
mRingBufferFrameSize(0), |
mDriverStatus(NULL), |
mInputStreamObjectID(SA_ObjectMap::GetNextObjectID()), |
mInputStreamIsActive(true), |
mInputStreamRingBuffer(NULL), |
mOutputStreamObjectID(SA_ObjectMap::GetNextObjectID()), |
mOutputStreamIsActive(true), |
mOutputStreamRingBuffer(NULL), |
mInputMasterVolumeControlObjectID(SA_ObjectMap::GetNextObjectID()), |
mInputMasterVolumeControlRawValueShadow(kSimpleAudioDriver_Control_MinRawVolumeValue), |
mOutputMasterVolumeControlObjectID(SA_ObjectMap::GetNextObjectID()), |
mOutputMasterVolumeControlRawValueShadow(kSimpleAudioDriver_Control_MinRawVolumeValue), |
mVolumeCurve() |
{ |
// Setup the volume curve with the one range |
mVolumeCurve.AddRange(kSimpleAudioDriver_Control_MinRawVolumeValue, kSimpleAudioDriver_Control_MaxRawVolumeValue, kSimpleAudioDriver_Control_MinDBVolumeValue, kSimpleAudioDriver_Control_MaxDbVolumeValue); |
} |
|
void SA_Device::Activate() |
{ |
// Open the connection to the driver and initialize things. |
_HW_Open(); |
|
// map the subobject IDs to this object |
SA_ObjectMap::MapObject(mInputStreamObjectID, this); |
SA_ObjectMap::MapObject(mOutputStreamObjectID, this); |
SA_ObjectMap::MapObject(mInputMasterVolumeControlObjectID, this); |
SA_ObjectMap::MapObject(mOutputMasterVolumeControlObjectID, this); |
|
// call the super-class, which just marks the object as active |
SA_Object::Activate(); |
} |
|
void SA_Device::Deactivate() |
{ |
// When this method is called, the obejct is basically dead, but we still need to be thread |
// safe. In this case, we also need to be safe vs. any IO threads, so we need to take both |
// locks. |
CAMutex::Locker theStateLocker(mStateMutex); |
CAMutex::Locker theIOLocker(mIOMutex); |
|
// mark the object inactive by calling the super-class |
SA_Object::Deactivate(); |
|
// unmap the subobject IDs |
SA_ObjectMap::UnmapObject(mInputStreamObjectID, this); |
SA_ObjectMap::UnmapObject(mOutputStreamObjectID, this); |
SA_ObjectMap::UnmapObject(mInputMasterVolumeControlObjectID, this); |
SA_ObjectMap::UnmapObject(mOutputMasterVolumeControlObjectID, this); |
|
// close the connection to the driver |
_HW_Close(); |
} |
|
SA_Device::~SA_Device() |
{ |
} |
|
#pragma mark Property Operations |
|
bool SA_Device::HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const |
{ |
// This object implements several API-level objects. So the first thing to do is to figure out |
// which object this request is really for. Note that mSubObjectID is an invariant as this |
// driver's structure does not change dynamically. It will always have the parts it has. |
bool theAnswer = false; |
if(inObjectID == mObjectID) |
{ |
theAnswer = Device_HasProperty(inObjectID, inClientPID, inAddress); |
} |
else if((inObjectID == mInputStreamObjectID) || (inObjectID == mOutputStreamObjectID)) |
{ |
theAnswer = Stream_HasProperty(inObjectID, inClientPID, inAddress); |
} |
else if((inObjectID == mInputMasterVolumeControlObjectID) || (inObjectID == mOutputMasterVolumeControlObjectID)) |
{ |
theAnswer = Control_HasProperty(inObjectID, inClientPID, inAddress); |
} |
else |
{ |
Throw(CAException(kAudioHardwareBadObjectError)); |
} |
return theAnswer; |
} |
|
bool SA_Device::IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const |
{ |
bool theAnswer = false; |
if(inObjectID == mObjectID) |
{ |
theAnswer = Device_IsPropertySettable(inObjectID, inClientPID, inAddress); |
} |
else if((inObjectID == mInputStreamObjectID) || (inObjectID == mOutputStreamObjectID)) |
{ |
theAnswer = Stream_IsPropertySettable(inObjectID, inClientPID, inAddress); |
} |
else if((inObjectID == mInputMasterVolumeControlObjectID) || (inObjectID == mOutputMasterVolumeControlObjectID)) |
{ |
theAnswer = Control_IsPropertySettable(inObjectID, inClientPID, inAddress); |
} |
else |
{ |
Throw(CAException(kAudioHardwareBadObjectError)); |
} |
return theAnswer; |
} |
|
UInt32 SA_Device::GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const |
{ |
UInt32 theAnswer = 0; |
if(inObjectID == mObjectID) |
{ |
theAnswer = Device_GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData); |
} |
else if((inObjectID == mInputStreamObjectID) || (inObjectID == mOutputStreamObjectID)) |
{ |
theAnswer = Stream_GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData); |
} |
else if((inObjectID == mInputMasterVolumeControlObjectID) || (inObjectID == mOutputMasterVolumeControlObjectID)) |
{ |
theAnswer = Control_GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData); |
} |
else |
{ |
Throw(CAException(kAudioHardwareBadObjectError)); |
} |
return theAnswer; |
} |
|
void SA_Device::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const |
{ |
if(inObjectID == mObjectID) |
{ |
Device_GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData); |
} |
else if((inObjectID == mInputStreamObjectID) || (inObjectID == mOutputStreamObjectID)) |
{ |
Stream_GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData); |
} |
else if((inObjectID == mInputMasterVolumeControlObjectID) || (inObjectID == mOutputMasterVolumeControlObjectID)) |
{ |
Control_GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData); |
} |
else |
{ |
Throw(CAException(kAudioHardwareBadObjectError)); |
} |
} |
|
void SA_Device::SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData) |
{ |
if(inObjectID == mObjectID) |
{ |
Device_SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData); |
} |
else if((inObjectID == mInputStreamObjectID) || (inObjectID == mOutputStreamObjectID)) |
{ |
Stream_SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData); |
} |
else if((inObjectID == mInputMasterVolumeControlObjectID) || (inObjectID == mOutputMasterVolumeControlObjectID)) |
{ |
Control_SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData); |
} |
else |
{ |
Throw(CAException(kAudioHardwareBadObjectError)); |
} |
} |
|
#pragma mark Device Property Operations |
|
bool SA_Device::Device_HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Device_GetPropertyData() method. |
|
bool theAnswer = false; |
switch(inAddress.mSelector) |
{ |
case kAudioObjectPropertyName: |
case kAudioObjectPropertyManufacturer: |
case kAudioDevicePropertyDeviceUID: |
case kAudioDevicePropertyModelUID: |
case kAudioDevicePropertyTransportType: |
case kAudioDevicePropertyRelatedDevices: |
case kAudioDevicePropertyClockDomain: |
case kAudioDevicePropertyDeviceIsAlive: |
case kAudioDevicePropertyDeviceIsRunning: |
case kAudioObjectPropertyControlList: |
case kAudioDevicePropertyNominalSampleRate: |
case kAudioDevicePropertyAvailableNominalSampleRates: |
case kAudioDevicePropertyIsHidden: |
case kAudioDevicePropertyZeroTimeStampPeriod: |
case kAudioDevicePropertyStreams: |
theAnswer = true; |
break; |
|
case kAudioDevicePropertyLatency: |
case kAudioDevicePropertySafetyOffset: |
case kAudioDevicePropertyPreferredChannelsForStereo: |
case kAudioDevicePropertyPreferredChannelLayout: |
case kAudioDevicePropertyDeviceCanBeDefaultDevice: |
case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice: |
theAnswer = (inAddress.mScope == kAudioObjectPropertyScopeInput) || (inAddress.mScope == kAudioObjectPropertyScopeOutput); |
break; |
|
default: |
theAnswer = SA_Object::HasProperty(inObjectID, inClientPID, inAddress); |
break; |
}; |
return theAnswer; |
} |
|
bool SA_Device::Device_IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Device_GetPropertyData() method. |
|
bool theAnswer = false; |
switch(inAddress.mSelector) |
{ |
case kAudioObjectPropertyName: |
case kAudioObjectPropertyManufacturer: |
case kAudioDevicePropertyDeviceUID: |
case kAudioDevicePropertyModelUID: |
case kAudioDevicePropertyTransportType: |
case kAudioDevicePropertyRelatedDevices: |
case kAudioDevicePropertyClockDomain: |
case kAudioDevicePropertyDeviceIsAlive: |
case kAudioDevicePropertyDeviceIsRunning: |
case kAudioDevicePropertyDeviceCanBeDefaultDevice: |
case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice: |
case kAudioDevicePropertyLatency: |
case kAudioDevicePropertyStreams: |
case kAudioObjectPropertyControlList: |
case kAudioDevicePropertySafetyOffset: |
case kAudioDevicePropertyAvailableNominalSampleRates: |
case kAudioDevicePropertyIsHidden: |
case kAudioDevicePropertyPreferredChannelsForStereo: |
case kAudioDevicePropertyPreferredChannelLayout: |
case kAudioDevicePropertyZeroTimeStampPeriod: |
theAnswer = false; |
break; |
|
case kAudioDevicePropertyNominalSampleRate: |
theAnswer = true; |
break; |
|
default: |
theAnswer = SA_Object::IsPropertySettable(inObjectID, inClientPID, inAddress); |
break; |
}; |
return theAnswer; |
} |
|
UInt32 SA_Device::Device_GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Device_GetPropertyData() method. |
|
UInt32 theAnswer = 0; |
switch(inAddress.mSelector) |
{ |
case kAudioObjectPropertyName: |
theAnswer = sizeof(CFStringRef); |
break; |
|
case kAudioObjectPropertyManufacturer: |
theAnswer = sizeof(CFStringRef); |
break; |
|
case kAudioObjectPropertyOwnedObjects: |
switch(inAddress.mScope) |
{ |
case kAudioObjectPropertyScopeGlobal: |
theAnswer = kNumberOfSubObjects * sizeof(AudioObjectID); |
break; |
|
case kAudioObjectPropertyScopeInput: |
theAnswer = kNumberOfInputSubObjects * sizeof(AudioObjectID); |
break; |
|
case kAudioObjectPropertyScopeOutput: |
theAnswer = kNumberOfOutputSubObjects * sizeof(AudioObjectID); |
break; |
}; |
break; |
|
case kAudioDevicePropertyDeviceUID: |
theAnswer = sizeof(CFStringRef); |
break; |
|
case kAudioDevicePropertyModelUID: |
theAnswer = sizeof(CFStringRef); |
break; |
|
case kAudioDevicePropertyTransportType: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyRelatedDevices: |
theAnswer = sizeof(AudioObjectID); |
break; |
|
case kAudioDevicePropertyClockDomain: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyDeviceIsAlive: |
theAnswer = sizeof(AudioClassID); |
break; |
|
case kAudioDevicePropertyDeviceIsRunning: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyDeviceCanBeDefaultDevice: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyLatency: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyStreams: |
switch(inAddress.mScope) |
{ |
case kAudioObjectPropertyScopeGlobal: |
theAnswer = kNumberOfStreams * sizeof(AudioObjectID); |
break; |
|
case kAudioObjectPropertyScopeInput: |
theAnswer = kNumberOfInputStreams * sizeof(AudioObjectID); |
break; |
|
case kAudioObjectPropertyScopeOutput: |
theAnswer = kNumberOfOutputStreams * sizeof(AudioObjectID); |
break; |
}; |
break; |
|
case kAudioObjectPropertyControlList: |
theAnswer = kNumberOfControls * sizeof(AudioObjectID); |
break; |
|
case kAudioDevicePropertySafetyOffset: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyNominalSampleRate: |
theAnswer = sizeof(Float64); |
break; |
|
case kAudioDevicePropertyAvailableNominalSampleRates: |
theAnswer = 2 * sizeof(AudioValueRange); |
break; |
|
case kAudioDevicePropertyIsHidden: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyPreferredChannelsForStereo: |
theAnswer = 2 * sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyPreferredChannelLayout: |
theAnswer = offsetof(AudioChannelLayout, mChannelDescriptions) + (2 * sizeof(AudioChannelDescription)); |
break; |
|
case kAudioDevicePropertyZeroTimeStampPeriod: |
theAnswer = sizeof(UInt32); |
break; |
|
default: |
theAnswer = SA_Object::GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData); |
break; |
}; |
return theAnswer; |
} |
|
void SA_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. |
// Also, since most of the data that will get returned is static, there are few instances where |
// it is necessary to lock the state mutex. |
|
UInt32 theNumberItemsToFetch; |
UInt32 theItemIndex; |
switch(inAddress.mSelector) |
{ |
case kAudioObjectPropertyName: |
// This is the human readable name of the device. Note that in this case we return a |
// value that is a key into the localizable strings in this bundle. This allows us to |
// return a localized name for the device. |
ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioObjectPropertyManufacturer for the device"); |
*reinterpret_cast<CFStringRef*>(outData) = CFSTR("DeviceName"); |
outDataSize = sizeof(CFStringRef); |
break; |
|
case kAudioObjectPropertyManufacturer: |
// This is the human readable name of the maker of the plug-in. Note that in this case |
// we return a value that is a key into the localizable strings in this bundle. This |
// allows us to return a localized name for the manufacturer. |
ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioObjectPropertyManufacturer for the device"); |
*reinterpret_cast<CFStringRef*>(outData) = CFSTR("ManufacturerName"); |
outDataSize = sizeof(CFStringRef); |
break; |
|
case kAudioObjectPropertyOwnedObjects: |
// 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 |
theNumberItemsToFetch = inDataSize / sizeof(AudioObjectID); |
|
// The device owns its streams and controls. Note that what is returned here |
// depends on the scope requested. |
switch(inAddress.mScope) |
{ |
case kAudioObjectPropertyScopeGlobal: |
// global scope means return all objects |
if(theNumberItemsToFetch > kNumberOfSubObjects) |
{ |
theNumberItemsToFetch = kNumberOfSubObjects; |
} |
|
// fill out the list with as many objects as requested, which is everything |
if(theNumberItemsToFetch > 0) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[0] = mInputStreamObjectID; |
} |
if(theNumberItemsToFetch > 1) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[1] = mOutputStreamObjectID; |
} |
if(theNumberItemsToFetch > 2) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[2] = mInputMasterVolumeControlObjectID; |
} |
if(theNumberItemsToFetch > 3) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[3] = mOutputMasterVolumeControlObjectID; |
} |
break; |
|
case kAudioObjectPropertyScopeInput: |
// input scope means just the objects on the input side |
if(theNumberItemsToFetch > kNumberOfInputSubObjects) |
{ |
theNumberItemsToFetch = kNumberOfInputSubObjects; |
} |
|
// fill out the list with the right objects |
if(theNumberItemsToFetch > 0) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[0] = mInputStreamObjectID; |
} |
if(theNumberItemsToFetch > 1) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[1] = mInputMasterVolumeControlObjectID; |
} |
break; |
|
case kAudioObjectPropertyScopeOutput: |
// output scope means just the objects on the output side |
if(theNumberItemsToFetch > kNumberOfOutputSubObjects) |
{ |
theNumberItemsToFetch = kNumberOfOutputSubObjects; |
} |
|
// fill out the list with the right objects |
if(theNumberItemsToFetch > 0) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[0] = mOutputStreamObjectID; |
} |
if(theNumberItemsToFetch > 1) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[1] = mOutputMasterVolumeControlObjectID; |
} |
break; |
}; |
|
// report how much we wrote |
outDataSize = theNumberItemsToFetch * sizeof(AudioObjectID); |
break; |
|
case kAudioDevicePropertyDeviceUID: |
// This is a CFString that is a persistent token that can identify the same |
// audio device across boot sessions. Note that two instances of the same |
// device must have different values for this property. |
ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyDeviceUID for the device"); |
*reinterpret_cast<CFStringRef*>(outData) = mDeviceUID.CopyCFString(); |
outDataSize = sizeof(CFStringRef); |
break; |
|
case kAudioDevicePropertyModelUID: |
// This is a CFString that is a persistent token that can identify audio |
// devices that are the same kind of device. Note that two instances of the |
// save device must have the same value for this property. |
ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyModelUID for the device"); |
*reinterpret_cast<CFStringRef*>(outData) = CFSTR(kDeviceModelUID); |
outDataSize = sizeof(CFStringRef); |
break; |
|
case kAudioDevicePropertyTransportType: |
// This value represents how the device is attached to the system. This can be |
// any 32 bit integer, but common values for this property are defined in |
// <CoreAudio/AudioHardwareBase.h> |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyTransportType for the device"); |
*reinterpret_cast<UInt32*>(outData) = kAudioDeviceTransportTypeVirtual; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyRelatedDevices: |
// The related devices property identifies device objects that are very closely |
// related. Generally, this is for relating devices that are packaged together |
// in the hardware such as when the input side and the output side of a piece |
// of hardware can be clocked separately and therefore need to be represented |
// as separate AudioDevice objects. In such case, both devices would report |
// that they are related to each other. Note that at minimum, a device is |
// related to itself, so this list will always be at least one item long. |
|
// 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 |
theNumberItemsToFetch = inDataSize / sizeof(AudioObjectID); |
|
// we only have the one device... |
if(theNumberItemsToFetch > 1) |
{ |
theNumberItemsToFetch = 1; |
} |
|
// Write the devices' object IDs into the return value |
if(theNumberItemsToFetch > 0) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[0] = GetObjectID(); |
} |
|
// report how much we wrote |
outDataSize = theNumberItemsToFetch * sizeof(AudioObjectID); |
break; |
|
case kAudioDevicePropertyClockDomain: |
// This property allows the device to declare what other devices it is |
// synchronized with in hardware. The way it works is that if two devices have |
// the same value for this property and the value is not zero, then the two |
// devices are synchronized in hardware. Note that a device that either can't |
// be synchronized with others or doesn't know should return 0 for this |
// property. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyClockDomain for the device"); |
*reinterpret_cast<UInt32*>(outData) = 0; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyDeviceIsAlive: |
// This property returns whether or not the device is alive. Note that it is |
// note uncommon for a device to be dead but still momentarily availble in the |
// device list. In the case of this device, it will always be alive. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyDeviceIsAlive for the device"); |
*reinterpret_cast<UInt32*>(outData) = 1; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyDeviceIsRunning: |
// This property returns whether or not IO is running for the device. |
{ |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyDeviceIsRunning for the device"); |
|
// The IsRunning state is protected by the state lock |
CAMutex::Locker theStateLocker(mStateMutex); |
|
// return the state and how much data we are touching |
*reinterpret_cast<UInt32*>(outData) = mStartCount > 0; |
outDataSize = sizeof(UInt32); |
} |
break; |
|
case kAudioDevicePropertyDeviceCanBeDefaultDevice: |
// This property returns whether or not the device wants to be able to be the |
// default device for content. This is the device that iTunes and QuickTime |
// will use to play their content on and FaceTime will use as it's microhphone. |
// Nearly all devices should allow for this. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyDeviceCanBeDefaultDevice for the device"); |
*reinterpret_cast<UInt32*>(outData) = 1; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyDeviceCanBeDefaultSystemDevice: |
// This property returns whether or not the device wants to be the system |
// default device. This is the device that is used to play interface sounds and |
// other incidental or UI-related sounds on. Most devices should allow this |
// although devices with lots of latency may not want to. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyDeviceCanBeDefaultSystemDevice for the device"); |
*reinterpret_cast<UInt32*>(outData) = 1; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyLatency: |
// This property returns the presentation latency of the device. For this, |
// device, the value is 0 due to the fact that it always vends silence. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyLatency for the device"); |
*reinterpret_cast<UInt32*>(outData) = 0; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyStreams: |
// 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 |
theNumberItemsToFetch = inDataSize / sizeof(AudioObjectID); |
|
// Note that what is returned here depends on the scope requested. |
switch(inAddress.mScope) |
{ |
case kAudioObjectPropertyScopeGlobal: |
// global scope means return all streams |
if(theNumberItemsToFetch > kNumberOfStreams) |
{ |
theNumberItemsToFetch = kNumberOfStreams; |
} |
|
// fill out the list with as many objects as requested |
if(theNumberItemsToFetch > 0) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[0] = mInputStreamObjectID; |
} |
if(theNumberItemsToFetch > 1) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[1] = mOutputStreamObjectID; |
} |
break; |
|
case kAudioObjectPropertyScopeInput: |
// input scope means just the objects on the input side |
if(theNumberItemsToFetch > kNumberOfInputStreams) |
{ |
theNumberItemsToFetch = kNumberOfInputStreams; |
} |
|
// fill out the list with as many objects as requested |
if(theNumberItemsToFetch > 0) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[0] = mInputStreamObjectID; |
} |
break; |
|
case kAudioObjectPropertyScopeOutput: |
// output scope means just the objects on the output side |
if(theNumberItemsToFetch > kNumberOfOutputStreams) |
{ |
theNumberItemsToFetch = kNumberOfOutputStreams; |
} |
|
// fill out the list with as many objects as requested |
if(theNumberItemsToFetch > 0) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[0] = mOutputStreamObjectID; |
} |
break; |
}; |
|
// report how much we wrote |
outDataSize = theNumberItemsToFetch * sizeof(AudioObjectID); |
break; |
|
case kAudioObjectPropertyControlList: |
// 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 |
theNumberItemsToFetch = inDataSize / sizeof(AudioObjectID); |
if(theNumberItemsToFetch > kNumberOfControls) |
{ |
theNumberItemsToFetch = kNumberOfControls; |
} |
|
// fill out the list with as many objects as requested, which is everything |
if(theNumberItemsToFetch > 0) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[0] = mInputMasterVolumeControlObjectID; |
} |
if(theNumberItemsToFetch > 1) |
{ |
reinterpret_cast<AudioObjectID*>(outData)[1] = mOutputMasterVolumeControlObjectID; |
} |
|
// report how much we wrote |
outDataSize = theNumberItemsToFetch * sizeof(AudioObjectID); |
break; |
|
case kAudioDevicePropertySafetyOffset: |
// This property returns the how close to now the HAL can read and write. For |
// this, device, the value is 0 due to the fact that it always vends silence. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertySafetyOffset for the device"); |
*reinterpret_cast<UInt32*>(outData) = 0; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyNominalSampleRate: |
// This property returns the nominal sample rate of the device. Note that we |
// only need to take the state lock to get this value. |
{ |
ThrowIf(inDataSize < sizeof(Float64), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyNominalSampleRate for the device"); |
|
// The sample rate is protected by the state lock |
CAMutex::Locker theStateLocker(mStateMutex); |
|
// need to lock around fetching the sample rate |
*reinterpret_cast<Float64*>(outData) = static_cast<Float64>(_HW_GetSampleRate()); |
outDataSize = sizeof(Float64); |
} |
break; |
|
case kAudioDevicePropertyAvailableNominalSampleRates: |
// This returns all nominal sample rates the device supports as an array of |
// AudioValueRangeStructs. Note that for discrete sampler rates, the range |
// will have the minimum value equal to the maximum value. |
|
// 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 |
theNumberItemsToFetch = inDataSize / sizeof(AudioValueRange); |
|
// clamp it to the number of items we have |
if(theNumberItemsToFetch > 2) |
{ |
theNumberItemsToFetch = 2; |
} |
|
// fill out the return array |
if(theNumberItemsToFetch > 0) |
{ |
((AudioValueRange*)outData)[0].mMinimum = 44100.0; |
((AudioValueRange*)outData)[0].mMaximum = 44100.0; |
} |
if(theNumberItemsToFetch > 1) |
{ |
((AudioValueRange*)outData)[1].mMinimum = 48000.0; |
((AudioValueRange*)outData)[1].mMaximum = 48000.0; |
} |
|
// report how much we wrote |
outDataSize = theNumberItemsToFetch * sizeof(AudioValueRange); |
break; |
|
case kAudioDevicePropertyIsHidden: |
// This returns whether or not the device is visible to clients. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyIsHidden for the device"); |
*reinterpret_cast<UInt32*>(outData) = 0; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyPreferredChannelsForStereo: |
// This property returns which two channesl to use as left/right for stereo |
// data by default. Note that the channel numbers are 1-based. |
ThrowIf(inDataSize < (2 * sizeof(UInt32)), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyPreferredChannelsForStereo for the device"); |
((UInt32*)outData)[0] = 1; |
((UInt32*)outData)[1] = 2; |
outDataSize = 2 * sizeof(UInt32); |
break; |
|
case kAudioDevicePropertyPreferredChannelLayout: |
// This property returns the default AudioChannelLayout to use for the device |
// by default. For this device, we return a stereo ACL. |
{ |
// calcualte how big the |
UInt32 theACLSize = offsetof(AudioChannelLayout, mChannelDescriptions) + (2 * sizeof(AudioChannelDescription)); |
ThrowIf(inDataSize < theACLSize, CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyPreferredChannelLayout for the device"); |
((AudioChannelLayout*)outData)->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; |
((AudioChannelLayout*)outData)->mChannelBitmap = 0; |
((AudioChannelLayout*)outData)->mNumberChannelDescriptions = 2; |
for(theItemIndex = 0; theItemIndex < 2; ++theItemIndex) |
{ |
((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mChannelLabel = kAudioChannelLabel_Left + theItemIndex; |
((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mChannelFlags = 0; |
((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mCoordinates[0] = 0; |
((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mCoordinates[1] = 0; |
((AudioChannelLayout*)outData)->mChannelDescriptions[theItemIndex].mCoordinates[2] = 0; |
} |
outDataSize = theACLSize; |
} |
break; |
|
case kAudioDevicePropertyZeroTimeStampPeriod: |
// This property returns how many frames the HAL should expect to see between |
// successive sample times in the zero time stamps this device provides. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyZeroTimeStampPeriod for the device"); |
*reinterpret_cast<UInt32*>(outData) = mRingBufferFrameSize; |
outDataSize = sizeof(UInt32); |
break; |
|
default: |
SA_Object::GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData); |
break; |
}; |
} |
|
void SA_Device::Device_SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData) |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Device_GetPropertyData() method. |
|
switch(inAddress.mSelector) |
{ |
case kAudioDevicePropertyNominalSampleRate: |
// Changing the sample rate needs to be handled via the RequestConfigChange/PerformConfigChange machinery. |
{ |
// check the arguments |
ThrowIf(inDataSize != sizeof(Float64), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Device_SetPropertyData: wrong size for the data for kAudioDevicePropertyNominalSampleRate"); |
ThrowIf((*((const Float64*)inData) != 44100.0) && (*((const Float64*)inData) != 48000.0), CAException(kAudioHardwareIllegalOperationError), "SA_Device::Device_SetPropertyData: unsupported value for kAudioDevicePropertyNominalSampleRate"); |
|
// we need to lock around getting the current sample rate to compare against the new rate |
UInt64 theOldSampleRate = 0; |
{ |
CAMutex::Locker theStateLocker(mStateMutex); |
theOldSampleRate = _HW_GetSampleRate(); |
} |
|
// make sure that the new value is different than the old value |
UInt64 theNewSampleRate = static_cast<UInt64>(*reinterpret_cast<const Float64*>(inData)); |
if(theNewSampleRate != theOldSampleRate) |
{ |
// we dispatch this so that the change can happen asynchronously |
AudioObjectID theDeviceObjectID = GetObjectID(); |
CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{ |
SA_PlugIn::Host_RequestDeviceConfigurationChange(theDeviceObjectID, theNewSampleRate, NULL); |
}); |
} |
} |
break; |
|
default: |
SA_Object::SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData); |
break; |
}; |
} |
|
#pragma mark Stream Property Operations |
|
bool SA_Device::Stream_HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Stream_GetPropertyData() method. |
|
bool theAnswer = false; |
switch(inAddress.mSelector) |
{ |
case kAudioStreamPropertyIsActive: |
case kAudioStreamPropertyDirection: |
case kAudioStreamPropertyTerminalType: |
case kAudioStreamPropertyStartingChannel: |
case kAudioStreamPropertyLatency: |
case kAudioStreamPropertyVirtualFormat: |
case kAudioStreamPropertyPhysicalFormat: |
case kAudioStreamPropertyAvailableVirtualFormats: |
case kAudioStreamPropertyAvailablePhysicalFormats: |
theAnswer = true; |
break; |
|
default: |
theAnswer = SA_Object::HasProperty(inObjectID, inClientPID, inAddress); |
break; |
}; |
return theAnswer; |
} |
|
bool SA_Device::Stream_IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Stream_GetPropertyData() method. |
|
bool theAnswer = false; |
switch(inAddress.mSelector) |
{ |
case kAudioStreamPropertyDirection: |
case kAudioStreamPropertyTerminalType: |
case kAudioStreamPropertyStartingChannel: |
case kAudioStreamPropertyLatency: |
case kAudioStreamPropertyAvailableVirtualFormats: |
case kAudioStreamPropertyAvailablePhysicalFormats: |
theAnswer = false; |
break; |
|
case kAudioStreamPropertyIsActive: |
case kAudioStreamPropertyVirtualFormat: |
case kAudioStreamPropertyPhysicalFormat: |
theAnswer = true; |
break; |
|
default: |
theAnswer = SA_Object::IsPropertySettable(inObjectID, inClientPID, inAddress); |
break; |
}; |
return theAnswer; |
} |
|
UInt32 SA_Device::Stream_GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Stream_GetPropertyData() method. |
|
UInt32 theAnswer = 0; |
switch(inAddress.mSelector) |
{ |
case kAudioStreamPropertyIsActive: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioStreamPropertyDirection: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioStreamPropertyTerminalType: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioStreamPropertyStartingChannel: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioStreamPropertyLatency: |
theAnswer = sizeof(UInt32); |
break; |
|
case kAudioStreamPropertyVirtualFormat: |
case kAudioStreamPropertyPhysicalFormat: |
theAnswer = sizeof(AudioStreamBasicDescription); |
break; |
|
case kAudioStreamPropertyAvailableVirtualFormats: |
case kAudioStreamPropertyAvailablePhysicalFormats: |
theAnswer = 2 * sizeof(AudioStreamRangedDescription); |
break; |
|
default: |
theAnswer = SA_Object::GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData); |
break; |
}; |
return theAnswer; |
} |
|
void SA_Device::Stream_GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. |
// Also, since most of the data that will get returned is static, there are few instances where |
// it is necessary to lock the state mutex. |
|
UInt32 theNumberItemsToFetch; |
switch(inAddress.mSelector) |
{ |
case kAudioObjectPropertyBaseClass: |
// The base class for kAudioStreamClassID is kAudioObjectClassID |
ThrowIf(inDataSize < sizeof(AudioClassID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_GetPropertyData: not enough space for the return value of kAudioObjectPropertyBaseClass for the volume control"); |
*reinterpret_cast<AudioClassID*>(outData) = kAudioObjectClassID; |
outDataSize = sizeof(AudioClassID); |
break; |
|
case kAudioObjectPropertyClass: |
// Streams are of the class, kAudioStreamClassID |
ThrowIf(inDataSize < sizeof(AudioClassID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_GetPropertyData: not enough space for the return value of kAudioObjectPropertyClass for the volume control"); |
*reinterpret_cast<AudioClassID*>(outData) = kAudioStreamClassID; |
outDataSize = sizeof(AudioClassID); |
break; |
|
case kAudioObjectPropertyOwner: |
// The stream's owner is the device object |
ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_GetPropertyData: not enough space for the return value of kAudioObjectPropertyOwner for the volume control"); |
*reinterpret_cast<AudioObjectID*>(outData) = GetObjectID(); |
outDataSize = sizeof(AudioObjectID); |
break; |
|
case kAudioStreamPropertyIsActive: |
// This property tells the device whether or not the given stream is going to |
// be used for IO. Note that we need to take the state lock to examine this |
// value. |
{ |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_GetPropertyData: not enough space for the return value of kAudioStreamPropertyIsActive for the stream"); |
|
// lock the state mutex |
CAMutex::Locker theStateLocker(mStateMutex); |
|
// return the requested value |
*reinterpret_cast<UInt32*>(outData) = (inAddress.mScope == kAudioObjectPropertyScopeInput) ? mInputStreamIsActive : mOutputStreamIsActive; |
outDataSize = sizeof(UInt32); |
} |
break; |
|
case kAudioStreamPropertyDirection: |
// This returns whether the stream is an input stream or an output stream. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_GetPropertyData: not enough space for the return value of kAudioStreamPropertyDirection for the stream"); |
*reinterpret_cast<UInt32*>(outData) = (inObjectID == mInputStreamObjectID) ? 1 : 0; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioStreamPropertyTerminalType: |
// This returns a value that indicates what is at the other end of the stream |
// such as a speaker or headphones, or a microphone. Values for this property |
// are defined in <CoreAudio/AudioHardwareBase.h> |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_GetPropertyData: not enough space for the return value of kAudioStreamPropertyTerminalType for the stream"); |
*reinterpret_cast<UInt32*>(outData) = (inObjectID == mInputStreamObjectID) ? kAudioStreamTerminalTypeMicrophone : kAudioStreamTerminalTypeSpeaker; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioStreamPropertyStartingChannel: |
// This property returns the absolute channel number for the first channel in |
// the stream. For exmaple, if a device has two output streams with two |
// channels each, then the starting channel number for the first stream is 1 |
// and ths starting channel number fo the second stream is 3. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_GetPropertyData: not enough space for the return value of kAudioStreamPropertyStartingChannel for the stream"); |
*reinterpret_cast<UInt32*>(outData) = 1; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioStreamPropertyLatency: |
// This property returns any additonal presentation latency the stream has. |
ThrowIf(inDataSize < sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_GetPropertyData: not enough space for the return value of kAudioStreamPropertyStartingChannel for the stream"); |
*reinterpret_cast<UInt32*>(outData) = 0; |
outDataSize = sizeof(UInt32); |
break; |
|
case kAudioStreamPropertyVirtualFormat: |
case kAudioStreamPropertyPhysicalFormat: |
// This returns the current format of the stream in an AudioStreamBasicDescription. |
// For devices that don't override the mix operation, the virtual format has to be the |
// same as the physical format. |
{ |
ThrowIf(inDataSize < sizeof(AudioStreamBasicDescription), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_GetPropertyData: not enough space for the return value of kAudioStreamPropertyVirtualFormat for the stream"); |
|
// lock the state mutex |
CAMutex::Locker theStateLocker(mStateMutex); |
|
// This particular device always vends 16 bit native endian signed integers |
reinterpret_cast<AudioStreamBasicDescription*>(outData)->mSampleRate = static_cast<Float64>(_HW_GetSampleRate()); |
reinterpret_cast<AudioStreamBasicDescription*>(outData)->mFormatID = kAudioFormatLinearPCM; |
reinterpret_cast<AudioStreamBasicDescription*>(outData)->mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; |
reinterpret_cast<AudioStreamBasicDescription*>(outData)->mBytesPerPacket = 4; |
reinterpret_cast<AudioStreamBasicDescription*>(outData)->mFramesPerPacket = 1; |
reinterpret_cast<AudioStreamBasicDescription*>(outData)->mBytesPerFrame = 4; |
reinterpret_cast<AudioStreamBasicDescription*>(outData)->mChannelsPerFrame = 2; |
reinterpret_cast<AudioStreamBasicDescription*>(outData)->mBitsPerChannel = 16; |
outDataSize = sizeof(AudioStreamBasicDescription); |
} |
break; |
|
case kAudioStreamPropertyAvailableVirtualFormats: |
case kAudioStreamPropertyAvailablePhysicalFormats: |
// This returns an array of AudioStreamRangedDescriptions that describe what |
// formats are supported. |
|
// 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 |
theNumberItemsToFetch = inDataSize / sizeof(AudioStreamRangedDescription); |
|
// clamp it to the number of items we have |
if(theNumberItemsToFetch > 2) |
{ |
theNumberItemsToFetch = 2; |
} |
|
// fill out the return array |
if(theNumberItemsToFetch > 0) |
{ |
((AudioStreamRangedDescription*)outData)[0].mFormat.mSampleRate = 44100.0; |
((AudioStreamRangedDescription*)outData)[0].mFormat.mFormatID = kAudioFormatLinearPCM; |
((AudioStreamRangedDescription*)outData)[0].mFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; |
((AudioStreamRangedDescription*)outData)[0].mFormat.mBytesPerPacket = 4; |
((AudioStreamRangedDescription*)outData)[0].mFormat.mFramesPerPacket = 1; |
((AudioStreamRangedDescription*)outData)[0].mFormat.mBytesPerFrame = 4; |
((AudioStreamRangedDescription*)outData)[0].mFormat.mChannelsPerFrame = 2; |
((AudioStreamRangedDescription*)outData)[0].mFormat.mBitsPerChannel = 16; |
((AudioStreamRangedDescription*)outData)[0].mSampleRateRange.mMinimum = 44100.0; |
((AudioStreamRangedDescription*)outData)[0].mSampleRateRange.mMaximum = 44100.0; |
} |
if(theNumberItemsToFetch > 1) |
{ |
((AudioStreamRangedDescription*)outData)[1].mFormat.mSampleRate = 48000.0; |
((AudioStreamRangedDescription*)outData)[1].mFormat.mFormatID = kAudioFormatLinearPCM; |
((AudioStreamRangedDescription*)outData)[1].mFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; |
((AudioStreamRangedDescription*)outData)[1].mFormat.mBytesPerPacket = 4; |
((AudioStreamRangedDescription*)outData)[1].mFormat.mFramesPerPacket = 1; |
((AudioStreamRangedDescription*)outData)[1].mFormat.mBytesPerFrame = 4; |
((AudioStreamRangedDescription*)outData)[1].mFormat.mChannelsPerFrame = 2; |
((AudioStreamRangedDescription*)outData)[1].mFormat.mBitsPerChannel = 16; |
((AudioStreamRangedDescription*)outData)[1].mSampleRateRange.mMinimum = 48000.0; |
((AudioStreamRangedDescription*)outData)[1].mSampleRateRange.mMaximum = 48000.0; |
} |
|
// report how much we wrote |
outDataSize = theNumberItemsToFetch * sizeof(AudioStreamRangedDescription); |
break; |
|
default: |
SA_Object::GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData); |
break; |
}; |
} |
|
void SA_Device::Stream_SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData) |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Stream_GetPropertyData() method. |
|
switch(inAddress.mSelector) |
{ |
case kAudioStreamPropertyIsActive: |
{ |
// Changing the active state of a stream doesn't affect IO or change the structure |
// so we can just save the state and send the notification. |
ThrowIf(inDataSize != sizeof(UInt32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_SetPropertyData: wrong size for the data for kAudioDevicePropertyNominalSampleRate"); |
bool theNewIsActive = *reinterpret_cast<const UInt32*>(inData) != 0; |
|
CAMutex::Locker theStateLocker(mStateMutex); |
if(inObjectID == mInputStreamObjectID) |
{ |
if(mInputStreamIsActive != theNewIsActive) |
{ |
mInputStreamIsActive = theNewIsActive; |
} |
} |
else |
{ |
if(mOutputStreamIsActive != theNewIsActive) |
{ |
mOutputStreamIsActive = theNewIsActive; |
} |
} |
} |
break; |
|
case kAudioStreamPropertyVirtualFormat: |
case kAudioStreamPropertyPhysicalFormat: |
{ |
// Changing the stream format needs to be handled via the |
// RequestConfigChange/PerformConfigChange machinery. Note that because this |
// device only supports 2 channel 32 bit float data, the only thing that can |
// change is the sample rate. |
ThrowIf(inDataSize != sizeof(AudioStreamBasicDescription), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Stream_SetPropertyData: wrong size for the data for kAudioStreamPropertyPhysicalFormat"); |
|
const AudioStreamBasicDescription* theNewFormat = reinterpret_cast<const AudioStreamBasicDescription*>(inData); |
ThrowIf(theNewFormat->mFormatID != kAudioFormatLinearPCM, CAException(kAudioDeviceUnsupportedFormatError), "SA_Device::Stream_SetPropertyData: unsupported format ID for kAudioStreamPropertyPhysicalFormat"); |
ThrowIf(theNewFormat->mFormatFlags != (kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked), CAException(kAudioDeviceUnsupportedFormatError), "SA_Device::Stream_SetPropertyData: unsupported format flags for kAudioStreamPropertyPhysicalFormat"); |
ThrowIf(theNewFormat->mBytesPerPacket != 8, CAException(kAudioDeviceUnsupportedFormatError), "SA_Device::Stream_SetPropertyData: unsupported bytes per packet for kAudioStreamPropertyPhysicalFormat"); |
ThrowIf(theNewFormat->mFramesPerPacket != 1, CAException(kAudioDeviceUnsupportedFormatError), "SA_Device::Stream_SetPropertyData: unsupported frames per packet for kAudioStreamPropertyPhysicalFormat"); |
ThrowIf(theNewFormat->mBytesPerFrame != 8, CAException(kAudioDeviceUnsupportedFormatError), "SA_Device::Stream_SetPropertyData: unsupported bytes per frame for kAudioStreamPropertyPhysicalFormat"); |
ThrowIf(theNewFormat->mChannelsPerFrame != 2, CAException(kAudioDeviceUnsupportedFormatError), "SA_Device::Stream_SetPropertyData: unsupported channels per frame for kAudioStreamPropertyPhysicalFormat"); |
ThrowIf(theNewFormat->mBitsPerChannel != 32, CAException(kAudioDeviceUnsupportedFormatError), "SA_Device::Stream_SetPropertyData: unsupported bits per channel for kAudioStreamPropertyPhysicalFormat"); |
ThrowIf((theNewFormat->mSampleRate != 44100.0) && (theNewFormat->mSampleRate != 48000.0), CAException(kAudioDeviceUnsupportedFormatError), "SA_Device::Stream_SetPropertyData: unsupported sample rate for kAudioStreamPropertyPhysicalFormat"); |
|
// we need to lock around getting the current sample rate to compare against the new rate |
UInt64 theOldSampleRate = 0; |
{ |
CAMutex::Locker theStateLocker(mStateMutex); |
theOldSampleRate = _HW_GetSampleRate(); |
} |
|
// make sure that the new value is different than the old value |
UInt64 theNewSampleRate = static_cast<UInt64>(*reinterpret_cast<const Float64*>(inData)); |
if(theNewSampleRate != theOldSampleRate) |
{ |
// we dispatch this so that the change can happen asynchronously |
AudioObjectID theDeviceObjectID = GetObjectID(); |
CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{ |
SA_PlugIn::Host_RequestDeviceConfigurationChange(theDeviceObjectID, theNewSampleRate, NULL); |
}); |
} |
} |
break; |
|
default: |
SA_Object::SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData); |
break; |
}; |
} |
|
#pragma mark Control Property Operations |
|
bool SA_Device::Control_HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Control_GetPropertyData() method. |
|
bool theAnswer = false; |
switch(inAddress.mSelector) |
{ |
case kAudioControlPropertyScope: |
case kAudioControlPropertyElement: |
case kAudioLevelControlPropertyScalarValue: |
case kAudioLevelControlPropertyDecibelValue: |
case kAudioLevelControlPropertyDecibelRange: |
case kAudioLevelControlPropertyConvertScalarToDecibels: |
case kAudioLevelControlPropertyConvertDecibelsToScalar: |
theAnswer = true; |
break; |
|
default: |
theAnswer = SA_Object::HasProperty(inObjectID, inClientPID, inAddress); |
break; |
}; |
return theAnswer; |
} |
|
bool SA_Device::Control_IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Control_GetPropertyData() method. |
|
bool theAnswer = false; |
switch(inAddress.mSelector) |
{ |
case kAudioControlPropertyScope: |
case kAudioControlPropertyElement: |
case kAudioLevelControlPropertyDecibelRange: |
case kAudioLevelControlPropertyConvertScalarToDecibels: |
case kAudioLevelControlPropertyConvertDecibelsToScalar: |
theAnswer = false; |
break; |
|
case kAudioLevelControlPropertyScalarValue: |
case kAudioLevelControlPropertyDecibelValue: |
theAnswer = true; |
break; |
|
default: |
theAnswer = SA_Object::IsPropertySettable(inObjectID, inClientPID, inAddress); |
break; |
}; |
return theAnswer; |
} |
|
UInt32 SA_Device::Control_GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Control_GetPropertyData() method. |
|
UInt32 theAnswer = 0; |
switch(inAddress.mSelector) |
{ |
case kAudioControlPropertyScope: |
theAnswer = sizeof(AudioObjectPropertyScope); |
break; |
|
case kAudioControlPropertyElement: |
theAnswer = sizeof(AudioObjectPropertyElement); |
break; |
|
case kAudioLevelControlPropertyScalarValue: |
theAnswer = sizeof(Float32); |
break; |
|
case kAudioLevelControlPropertyDecibelValue: |
theAnswer = sizeof(Float32); |
break; |
|
case kAudioLevelControlPropertyDecibelRange: |
theAnswer = sizeof(AudioValueRange); |
break; |
|
case kAudioLevelControlPropertyConvertScalarToDecibels: |
theAnswer = sizeof(Float32); |
break; |
|
case kAudioLevelControlPropertyConvertDecibelsToScalar: |
theAnswer = sizeof(Float32); |
break; |
|
default: |
theAnswer = SA_Object::GetPropertyDataSize(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData); |
break; |
}; |
return theAnswer; |
} |
|
void SA_Device::Control_GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. |
// Also, since most of the data that will get returned is static, there are few instances where |
// it is necessary to lock the state mutex. |
|
SInt32 theControlRawValue; |
Float32 theVolumeValue; |
switch(inAddress.mSelector) |
{ |
case kAudioObjectPropertyBaseClass: |
// The base class for kAudioVolumeControlClassID is kAudioLevelControlClassID |
ThrowIf(inDataSize < sizeof(AudioClassID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioObjectPropertyBaseClass for the volume control"); |
*reinterpret_cast<AudioClassID*>(outData) = kAudioLevelControlClassID; |
outDataSize = sizeof(AudioClassID); |
break; |
|
case kAudioObjectPropertyClass: |
// Volume controls are of the class, kAudioVolumeControlClassID |
ThrowIf(inDataSize < sizeof(AudioClassID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioObjectPropertyClass for the volume control"); |
*reinterpret_cast<AudioClassID*>(outData) = kAudioVolumeControlClassID; |
outDataSize = sizeof(AudioClassID); |
break; |
|
case kAudioObjectPropertyOwner: |
// The control's owner is the device object |
ThrowIf(inDataSize < sizeof(AudioObjectID), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioObjectPropertyOwner for the volume control"); |
*reinterpret_cast<AudioObjectID*>(outData) = GetObjectID(); |
outDataSize = sizeof(AudioObjectID); |
break; |
|
case kAudioControlPropertyScope: |
// This property returns the scope that the control is attached to. |
ThrowIf(inDataSize < sizeof(AudioObjectPropertyScope), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioControlPropertyScope for the volume control"); |
*reinterpret_cast<AudioObjectPropertyScope*>(outData) = (inObjectID == mInputMasterVolumeControlObjectID) ? kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput; |
outDataSize = sizeof(AudioObjectPropertyScope); |
break; |
|
case kAudioControlPropertyElement: |
// This property returns the element that the control is attached to. |
ThrowIf(inDataSize < sizeof(AudioObjectPropertyElement), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioControlPropertyElement for the volume control"); |
*reinterpret_cast<AudioObjectPropertyElement*>(outData) = kAudioObjectPropertyElementMaster; |
outDataSize = sizeof(AudioObjectPropertyElement); |
break; |
|
case kAudioLevelControlPropertyScalarValue: |
// This returns the value of the control in the normalized range of 0 to 1. |
{ |
ThrowIf(inDataSize < sizeof(Float32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioLevelControlPropertyScalarValue for the volume control"); |
CAMutex::Locker theStateLocker(mStateMutex); |
theControlRawValue = _HW_GetVolumeControlValue((inObjectID == mInputMasterVolumeControlObjectID) ? kSimpleAudioDriver_Control_MasterInputVolume : kSimpleAudioDriver_Control_MasterOutputVolume); |
*reinterpret_cast<Float32*>(outData) = mVolumeCurve.ConvertRawToScalar(theControlRawValue); |
outDataSize = sizeof(Float32); |
} |
break; |
|
case kAudioLevelControlPropertyDecibelValue: |
// This returns the dB value of the control. |
{ |
ThrowIf(inDataSize < sizeof(Float32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioLevelControlPropertyDecibelValue for the volume control"); |
CAMutex::Locker theStateLocker(mStateMutex); |
theControlRawValue = _HW_GetVolumeControlValue((inObjectID == mInputMasterVolumeControlObjectID) ? kSimpleAudioDriver_Control_MasterInputVolume : kSimpleAudioDriver_Control_MasterOutputVolume); |
*reinterpret_cast<Float32*>(outData) = mVolumeCurve.ConvertRawToDB(theControlRawValue); |
outDataSize = sizeof(Float32); |
} |
break; |
|
case kAudioLevelControlPropertyDecibelRange: |
// This returns the dB range of the control. |
ThrowIf(inDataSize < sizeof(AudioValueRange), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioLevelControlPropertyDecibelRange for the volume control"); |
reinterpret_cast<AudioValueRange*>(outData)->mMinimum = mVolumeCurve.GetMinimumDB(); |
reinterpret_cast<AudioValueRange*>(outData)->mMaximum = mVolumeCurve.GetMaximumDB(); |
outDataSize = sizeof(AudioValueRange); |
break; |
|
case kAudioLevelControlPropertyConvertScalarToDecibels: |
// This takes the scalar value in outData and converts it to dB. |
ThrowIf(inDataSize < sizeof(Float32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioLevelControlPropertyDecibelValue for the volume control"); |
|
// clamp the value to be between 0 and 1 |
theVolumeValue = *reinterpret_cast<Float32*>(outData); |
theVolumeValue = std::min(1.0f, std::max(0.0f, theVolumeValue)); |
|
// do the conversion |
*reinterpret_cast<Float32*>(outData) = mVolumeCurve.ConvertScalarToDB(theVolumeValue); |
|
// report how much we wrote |
outDataSize = sizeof(Float32); |
break; |
|
case kAudioLevelControlPropertyConvertDecibelsToScalar: |
// This takes the dB value in outData and converts it to scalar. |
ThrowIf(inDataSize < sizeof(Float32), CAException(kAudioHardwareBadPropertySizeError), "SA_Device::Control_GetPropertyData: not enough space for the return value of kAudioLevelControlPropertyDecibelValue for the volume control"); |
|
// clamp the value to be between kVolume_MinDB and kVolume_MaxDB |
theVolumeValue = *reinterpret_cast<Float32*>(outData); |
theVolumeValue = std::min(kSimpleAudioDriver_Control_MaxDbVolumeValue, std::max(kSimpleAudioDriver_Control_MinDBVolumeValue, theVolumeValue)); |
|
// do the conversion |
*reinterpret_cast<Float32*>(outData) = mVolumeCurve.ConvertDBToScalar(theVolumeValue); |
|
// report how much we wrote |
outDataSize = sizeof(Float32); |
break; |
|
default: |
SA_Object::GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData); |
break; |
}; |
} |
|
void SA_Device::Control_SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData) |
{ |
// For each object, this driver implements all the required properties plus a few extras that |
// are useful but not required. There is more detailed commentary about each property in the |
// Control_GetPropertyData() method. |
|
bool sendNotifications = false; |
kern_return_t theError = 0; |
Float32 theNewVolumeValue; |
SInt32 theNewRawVolumeValue; |
switch(inAddress.mSelector) |
{ |
case kAudioLevelControlPropertyScalarValue: |
// For the scalar volume, we clamp the new value to [0, 1]. Note that if this |
// value changes, it implies that the dB value changed too. |
{ |
ThrowIf(inDataSize != sizeof(Float32), CAException(kAudioHardwareBadPropertySizeError), "NullAudio_SetControlPropertyData: wrong size for the data for kAudioLevelControlPropertyScalarValue"); |
theNewVolumeValue = *((const Float32*)inData); |
theNewVolumeValue = std::min(1.0f, std::max(0.0f, theNewVolumeValue)); |
theNewRawVolumeValue = mVolumeCurve.ConvertScalarToRaw(theNewVolumeValue); |
CAMutex::Locker theStateLocker(mStateMutex); |
theError = _HW_SetVolumeControlValue((inObjectID == mInputMasterVolumeControlObjectID) ? kSimpleAudioDriver_Control_MasterInputVolume : kSimpleAudioDriver_Control_MasterOutputVolume, theNewRawVolumeValue); |
sendNotifications = theError == 0; |
} |
break; |
|
case kAudioLevelControlPropertyDecibelValue: |
// For the dB value, we first convert it to a scalar value since that is how |
// the value is tracked. Note that if this value changes, it implies that the |
// scalar value changes as well. |
{ |
ThrowIf(inDataSize != sizeof(Float32), CAException(kAudioHardwareBadPropertySizeError), "NullAudio_SetControlPropertyData: wrong size for the data for kAudioLevelControlPropertyScalarValue"); |
theNewVolumeValue = *((const Float32*)inData); |
theNewVolumeValue = std::min(kSimpleAudioDriver_Control_MaxDbVolumeValue, std::max(kSimpleAudioDriver_Control_MinDBVolumeValue, theNewVolumeValue)); |
theNewRawVolumeValue = mVolumeCurve.ConvertDBToRaw(theNewVolumeValue); |
CAMutex::Locker theStateLocker(mStateMutex); |
theError = _HW_SetVolumeControlValue((inObjectID == mInputMasterVolumeControlObjectID) ? kSimpleAudioDriver_Control_MasterInputVolume : kSimpleAudioDriver_Control_MasterOutputVolume, theNewRawVolumeValue); |
sendNotifications = theError == 0; |
} |
break; |
|
default: |
SA_Object::SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData); |
break; |
}; |
|
if(sendNotifications) |
{ |
CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{ |
AudioObjectPropertyAddress theChangedProperties[] = { { kAudioLevelControlPropertyScalarValue, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }, { kAudioLevelControlPropertyDecibelValue, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster } }; |
SA_PlugIn::Host_PropertiesChanged(inObjectID, 2, theChangedProperties); |
}); |
} |
} |
|
#pragma mark IO Operations |
|
void SA_Device::StartIO() |
{ |
// Starting/Stopping IO needs to be reference counted due to the possibility of multiple clients starting IO |
CAMutex::Locker theStateLocker(mStateMutex); |
|
// make sure we can start |
ThrowIf(mStartCount == UINT64_MAX, CAException(kAudioHardwareIllegalOperationError), "SA_Device::StartIO: failed to start because the ref count was maxxed out already"); |
|
// we only tell the hardware to start if this is the first time IO has been started |
if(mStartCount == 0) |
{ |
kern_return_t theError = _HW_StartIO(); |
ThrowIfKernelError(theError, CAException(theError), "SA_Device::StartIO: failed to start because of an error calling down to the driver"); |
} |
++mStartCount; |
} |
|
void SA_Device::StopIO() |
{ |
// Starting/Stopping IO needs to be reference counted due to the possibility of multiple clients starting IO |
CAMutex::Locker theStateLocker(mStateMutex); |
|
// we tell the hardware to stop if this is the last stop call |
if(mStartCount == 1) |
{ |
_HW_StopIO(); |
mStartCount = 0; |
} |
else if(mStartCount > 1) |
{ |
--mStartCount; |
} |
} |
|
void SA_Device::GetZeroTimeStamp(Float64& outSampleTime, UInt64& outHostTime, UInt64& outSeed) const |
{ |
// accessing the mapped memory requires holding the IO mutex |
CAMutex::Locker theIOLocker(mIOMutex); |
|
// read from the engine status struct in a loop to guarantee consistency |
UInt64 theSampleTime1; |
UInt64 theSampleTime2; |
UInt64 theHostTime1; |
UInt64 theHostTime2; |
do |
{ |
theHostTime1 = mDriverStatus->mHostTime; |
theSampleTime1 = mDriverStatus->mSampleTime; |
theHostTime2 = mDriverStatus->mHostTime; |
theSampleTime2 = mDriverStatus->mSampleTime; |
} |
while((theSampleTime1 != theSampleTime2) || (theHostTime1 != theHostTime2)); |
|
// set the return values |
outSampleTime = theSampleTime1; |
outHostTime = theHostTime1; |
outSeed = 0; |
} |
|
void SA_Device::WillDoIOOperation(UInt32 inOperationID, bool& outWillDo, bool& outWillDoInPlace) const |
{ |
switch(inOperationID) |
{ |
case kAudioServerPlugInIOOperationReadInput: |
case kAudioServerPlugInIOOperationWriteMix: |
outWillDo = true; |
outWillDoInPlace = true; |
break; |
|
case kAudioServerPlugInIOOperationThread: |
case kAudioServerPlugInIOOperationCycle: |
case kAudioServerPlugInIOOperationConvertInput: |
case kAudioServerPlugInIOOperationProcessInput: |
case kAudioServerPlugInIOOperationProcessOutput: |
case kAudioServerPlugInIOOperationMixOutput: |
case kAudioServerPlugInIOOperationProcessMix: |
case kAudioServerPlugInIOOperationConvertMix: |
default: |
outWillDo = false; |
outWillDoInPlace = true; |
break; |
|
}; |
} |
|
void SA_Device::BeginIOOperation(UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo& inIOCycleInfo) |
{ |
#pragma unused(inOperationID, inIOBufferFrameSize, inIOCycleInfo) |
} |
|
void SA_Device::DoIOOperation(AudioObjectID inStreamObjectID, UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo& inIOCycleInfo, void* ioMainBuffer, void* ioSecondaryBuffer) |
{ |
#pragma unused(inStreamObjectID, ioSecondaryBuffer) |
switch(inOperationID) |
{ |
case kAudioServerPlugInIOOperationReadInput: |
ReadInputData(inIOBufferFrameSize, inIOCycleInfo.mInputTime.mSampleTime, ioMainBuffer); |
break; |
|
case kAudioServerPlugInIOOperationWriteMix: |
WriteOutputData(inIOBufferFrameSize, inIOCycleInfo.mOutputTime.mSampleTime, ioMainBuffer); |
break; |
}; |
} |
|
void SA_Device::EndIOOperation(UInt32 inOperationID, UInt32 inIOBufferFrameSize, const AudioServerPlugInIOCycleInfo& inIOCycleInfo) |
{ |
#pragma unused(inOperationID, inIOBufferFrameSize, inIOCycleInfo) |
} |
|
void SA_Device::ReadInputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, void* outBuffer) |
{ |
// we need to be holding the IO lock to do this |
CAMutex::Locker theIOLocker(mIOMutex); |
|
// figure out where we are starting |
UInt64 theSampleTime = static_cast<UInt64>(inSampleTime); |
UInt32 theStartFrameOffset = theSampleTime % mRingBufferFrameSize; |
|
// figure out how many frames we need to copy |
UInt32 theNumberFramesToCopy1 = inIOBufferFrameSize; |
UInt32 theNumberFramesToCopy2 = 0; |
if((theStartFrameOffset + theNumberFramesToCopy1) > mRingBufferFrameSize) |
{ |
theNumberFramesToCopy1 = mRingBufferFrameSize - theStartFrameOffset; |
theNumberFramesToCopy2 = inIOBufferFrameSize - theNumberFramesToCopy1; |
} |
|
// do the copying (the byte sizes here assume a 16 bit stereo sample format) |
Byte* theDestination = reinterpret_cast<Byte*>(outBuffer); |
memcpy(theDestination, mInputStreamRingBuffer + (theStartFrameOffset * 4), theNumberFramesToCopy1 * 4); |
if(theNumberFramesToCopy2 > 0) |
{ |
memcpy(theDestination + (theNumberFramesToCopy1 * 4), mInputStreamRingBuffer, theNumberFramesToCopy2 * 4); |
} |
} |
|
void SA_Device::WriteOutputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, const void* inBuffer) |
{ |
// we need to be holding the IO lock to do this |
CAMutex::Locker theIOLocker(mIOMutex); |
|
// figure out where we are starting |
UInt64 theSampleTime = static_cast<UInt64>(inSampleTime); |
UInt32 theStartFrameOffset = theSampleTime % mRingBufferFrameSize; |
|
// figure out how many frames we need to copy |
UInt32 theNumberFramesToCopy1 = inIOBufferFrameSize; |
UInt32 theNumberFramesToCopy2 = 0; |
if((theStartFrameOffset + theNumberFramesToCopy1) > mRingBufferFrameSize) |
{ |
theNumberFramesToCopy1 = mRingBufferFrameSize - theStartFrameOffset; |
theNumberFramesToCopy2 = inIOBufferFrameSize - theNumberFramesToCopy1; |
} |
|
// do the copying (the byte sizes here assume a 16 bit stereo sample format) |
const Byte* theSource = reinterpret_cast<const Byte*>(inBuffer); |
memcpy(mOutputStreamRingBuffer + (theStartFrameOffset * 4), theSource, theNumberFramesToCopy1 * 4); |
if(theNumberFramesToCopy2 > 0) |
{ |
memcpy(mOutputStreamRingBuffer, theSource + (theNumberFramesToCopy1 * 4), theNumberFramesToCopy2 * 4); |
} |
} |
|
#pragma mark Hardware Accessors |
|
CFStringRef SA_Device::HW_CopyDeviceUID(io_object_t inIOObject) |
{ |
CFStringRef theAnswer = NULL; |
SA_IOKitObject::CopyProperty_CFString(inIOObject, CFSTR(kSimpleAudioDriver_RegistryKey_DeviceUID), theAnswer); |
return theAnswer; |
} |
|
void SA_Device::_HW_Open() |
{ |
// open the connection to the IOKit object |
mIOKitObject.OpenConnection(); |
|
// open the user-client |
mIOKitObject.CallMethod(kSimpleAudioDriver_Method_Open, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL); |
|
// map in the buffers |
UInt32 theBufferSize = 0; |
mDriverStatus = reinterpret_cast<SimpleAudioDriverStatus*>(mIOKitObject.MapMemory(kSimpleAudioDriver_Buffer_Status, kIOMapAnywhere, theBufferSize)); |
mInputStreamRingBuffer = reinterpret_cast<Byte*>(mIOKitObject.MapMemory(kSimpleAudioDriver_Buffer_Input, kIOMapAnywhere, theBufferSize)); |
mOutputStreamRingBuffer = reinterpret_cast<Byte*>(mIOKitObject.MapMemory(kSimpleAudioDriver_Buffer_Output, kIOMapAnywhere, theBufferSize)); |
|
// get the sample rate, ring buffer size, and control values to prime the shadows |
_HW_GetSampleRate(); |
_HW_GetRingBufferFrameSize(); |
_HW_GetVolumeControlValue(kSimpleAudioDriver_Control_MasterInputVolume); |
_HW_GetVolumeControlValue(kSimpleAudioDriver_Control_MasterOutputVolume); |
} |
|
void SA_Device::_HW_Close() |
{ |
// release the buffers |
mIOKitObject.ReleaseMemory(mDriverStatus, kSimpleAudioDriver_Buffer_Status); |
mIOKitObject.ReleaseMemory(mInputStreamRingBuffer, kSimpleAudioDriver_Buffer_Input); |
mIOKitObject.ReleaseMemory(mOutputStreamRingBuffer, kSimpleAudioDriver_Buffer_Output); |
|
// close the user client |
mIOKitObject.CallMethod(kSimpleAudioDriver_Method_Close, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL); |
|
// close the connection |
mIOKitObject.CloseConnection(); |
} |
|
kern_return_t SA_Device::_HW_StartIO() |
{ |
return mIOKitObject.CallMethod(kSimpleAudioDriver_Method_StartHardware, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL); |
} |
|
void SA_Device::_HW_StopIO() |
{ |
mIOKitObject.CallMethod(kSimpleAudioDriver_Method_StopHardware, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL); |
} |
|
UInt64 SA_Device::_HW_GetSampleRate() const |
{ |
mIOKitObject.CopyProperty_UInt64(CFSTR(kSimpleAudioDriver_RegistryKey_SampleRate), const_cast<SA_Device*>(this)->mSampleRateShadow); |
return mSampleRateShadow; |
} |
|
kern_return_t SA_Device::_HW_SetSampleRate(UInt64 inNewSampleRate) |
{ |
return mIOKitObject.CallMethod(kSimpleAudioDriver_Method_SetSampleRate, &inNewSampleRate, 1, NULL, 0, NULL, NULL, NULL, NULL); |
} |
|
UInt32 SA_Device::_HW_GetRingBufferFrameSize() const |
{ |
mIOKitObject.CopyProperty_UInt32(CFSTR(kSimpleAudioDriver_RegistryKey_RingBufferFrameSize), const_cast<SA_Device*>(this)->mRingBufferFrameSize); |
return mRingBufferFrameSize; |
} |
|
SInt32 SA_Device::_HW_GetVolumeControlValue(int inControlID) const |
{ |
// get the value from the kernel |
UInt64 theControlID = static_cast<UInt64>(inControlID); |
UInt64 theControlValue = 0; |
UInt32 theNumberOutputArguments = 1; |
const_cast<SA_Device*>(this)->mIOKitObject.CallMethod(kSimpleAudioDriver_Method_GetControlValue, &theControlID, 1, NULL, 0, &theControlValue, &theNumberOutputArguments, NULL, NULL); |
|
// store the new value in the shadow |
switch(inControlID) |
{ |
case kSimpleAudioDriver_Control_MasterInputVolume: |
const_cast<SA_Device*>(this)->mInputMasterVolumeControlRawValueShadow = static_cast<SInt32>(theControlValue); |
break; |
|
case kSimpleAudioDriver_Control_MasterOutputVolume: |
const_cast<SA_Device*>(this)->mOutputMasterVolumeControlRawValueShadow = static_cast<SInt32>(theControlValue); |
break; |
}; |
|
// return the value |
return static_cast<SInt32>(theControlValue); |
} |
|
kern_return_t SA_Device::_HW_SetVolumeControlValue(int inControlID, SInt32 inNewControlValue) |
{ |
UInt64 theInputArguments[] = { static_cast<UInt64>(inControlID), static_cast<UInt64>(inNewControlValue) }; |
kern_return_t theError = mIOKitObject.CallMethod(kSimpleAudioDriver_Method_SetControlValue, theInputArguments, 2, NULL, 0, NULL, NULL, NULL, NULL); |
|
// make sure the new value is in the proper range |
inNewControlValue = std::min(std::max(kSimpleAudioDriver_Control_MinRawVolumeValue, inNewControlValue), kSimpleAudioDriver_Control_MaxRawVolumeValue); |
|
// if there wasn't an error, the new value was applied, so we need to update the shadow |
if(theError == 0) |
{ |
switch(inControlID) |
{ |
case kSimpleAudioDriver_Control_MasterInputVolume: |
mInputMasterVolumeControlRawValueShadow = inNewControlValue; |
break; |
|
case kSimpleAudioDriver_Control_MasterOutputVolume: |
mOutputMasterVolumeControlRawValueShadow = inNewControlValue; |
break; |
}; |
} |
|
return theError; |
} |
|
#pragma mark Implementation |
|
void SA_Device::PerformConfigChange(UInt64 inChangeAction, void* inChangeInfo) |
{ |
#pragma unused(inChangeInfo) |
|
// this device only supports chagning the sample rate, which is stored in inChangeAction |
UInt64 theNewSampleRate = inChangeAction; |
|
// make sure we support the new sample rate |
if((theNewSampleRate == 44100) || (theNewSampleRate == 48000)) |
{ |
// we need to lock the state lock around telling the hardware about the new sample rate |
CAMutex::Locker theStateLocker(mStateMutex); |
_HW_SetSampleRate(theNewSampleRate); |
} |
} |
|
void SA_Device::AbortConfigChange(UInt64 inChangeAction, void* inChangeInfo) |
{ |
#pragma unused(inChangeAction, inChangeInfo) |
|
// this device doesn't need to do anything special if a change request gets aborted |
} |