// 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_Stream.cpp
// VCDriver
//
// Copyright © 2017 Kyle Neideck
// Copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Self Include
#include "VC_Stream.h"
// Local Includes
#include "VC_Types.h"
#include "VC_Utils.h"
#include "VC_Device.h"
#include "VC_PlugIn.h"
// PublicUtility Includes
#include "CADebugMacros.h"
#include "CAException.h"
#include "CAPropertyAddress.h"
#include "CADispatchQueue.h"
#pragma clang assume_nonnull begin
VC_Stream::VC_Stream(AudioObjectID inObjectID,
AudioDeviceID inOwnerDeviceID,
bool inIsInput,
Float64 inSampleRate,
UInt32 inStartingChannel)
:
VC_Object(inObjectID, kAudioStreamClassID, kAudioObjectClassID, inOwnerDeviceID),
mStateMutex(inIsInput ? "Input Stream State" : "Output Stream State"),
mIsInput(inIsInput),
mIsStreamActive(false),
mSampleRate(inSampleRate),
mStartingChannel(inStartingChannel)
{
}
VC_Stream::~VC_Stream()
{
}
bool VC_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
// 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 = VC_Object::HasProperty(inObjectID, inClientPID, inAddress);
break;
};
return theAnswer;
}
bool VC_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
// 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 = VC_Object::IsPropertySettable(inObjectID, inClientPID, inAddress);
break;
};
return theAnswer;
}
UInt32 VC_Stream::GetPropertyDataSize(AudioObjectID inObjectID,
pid_t inClientPID,
const AudioObjectPropertyAddress& inAddress,
UInt32 inQualifierDataSize,
const void* __nullable 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
// 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 = 1 * sizeof(AudioStreamRangedDescription);
break;
default:
theAnswer = VC_Object::GetPropertyDataSize(inObjectID,
inClientPID,
inAddress,
inQualifierDataSize,
inQualifierData);
break;
};
return theAnswer;
}
void VC_Stream::GetPropertyData(AudioObjectID inObjectID,
pid_t inClientPID,
const AudioObjectPropertyAddress& inAddress,
UInt32 inQualifierDataSize,
const void* __nullable inQualifierData,
UInt32 inDataSize,
UInt32& outDataSize,
void* outData) const
{
// Since most of the data that will get returned is static, there are few instances where it is
// necessary to lock the state mutex.
switch(inAddress.mSelector)
{
case kAudioObjectPropertyBaseClass:
// The base class for kAudioStreamClassID is kAudioObjectClassID
ThrowIf(inDataSize < sizeof(AudioClassID),
CAException(kAudioHardwareBadPropertySizeError),
"VC_Stream::GetPropertyData: not enough space for the return "
"value of kAudioObjectPropertyBaseClass for the stream");
*reinterpret_cast(outData) = kAudioObjectClassID;
outDataSize = sizeof(AudioClassID);
break;
case kAudioObjectPropertyClass:
// Streams are of the class kAudioStreamClassID
ThrowIf(inDataSize < sizeof(AudioClassID),
CAException(kAudioHardwareBadPropertySizeError),
"VC_Stream::GetPropertyData: not enough space for the return "
"value of kAudioObjectPropertyClass for the stream");
*reinterpret_cast(outData) = kAudioStreamClassID;
outDataSize = sizeof(AudioClassID);
break;
case kAudioObjectPropertyOwner:
// A stream's owner is a device object.
{
ThrowIf(inDataSize < sizeof(AudioObjectID),
CAException(kAudioHardwareBadPropertySizeError),
"VC_Stream::GetPropertyData: not enough space for the return "
"value of kAudioObjectPropertyOwner for the stream");
// Lock the state mutex to create a memory barrier, just in case a subclass ever
// allows mOwnerObjectID to be modified.
CAMutex::Locker theStateLocker(mStateMutex);
// Return the requested value.
*reinterpret_cast(outData) = mOwnerObjectID;
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),
"VC_Stream::GetPropertyData: not enough space for the return "
"value of kAudioStreamPropertyIsActive for the stream");
// Take the state lock.
CAMutex::Locker theStateLocker(mStateMutex);
// Return the requested value.
*reinterpret_cast(outData) = mIsStreamActive;
outDataSize = sizeof(UInt32);
}
break;
case kAudioStreamPropertyDirection:
// This returns whether the stream is an input or output stream.
ThrowIf(inDataSize < sizeof(UInt32),
CAException(kAudioHardwareBadPropertySizeError),
"VC_Stream::GetPropertyData: not enough space for the return value of "
"kAudioStreamPropertyDirection for the stream");
*reinterpret_cast(outData) = mIsInput ? 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.
ThrowIf(inDataSize < sizeof(UInt32),
CAException(kAudioHardwareBadPropertySizeError),
"VC_Stream::GetPropertyData: not enough space for the return value of "
"kAudioStreamPropertyTerminalType for the stream");
*reinterpret_cast(outData) =
mIsInput ? kAudioStreamTerminalTypeMicrophone : kAudioStreamTerminalTypeSpeaker;
outDataSize = sizeof(UInt32);
break;
case kAudioStreamPropertyStartingChannel:
// This property returns the absolute channel number for the first channel in
// the stream. For example, if a device has two output streams with two
// channels each, then the starting channel number for the first stream is 1
// and the starting channel number for the second stream is 3.
ThrowIf(inDataSize < sizeof(UInt32),
CAException(kAudioHardwareBadPropertySizeError),
"VC_Stream::GetPropertyData: not enough space for the return "
"value of kAudioStreamPropertyStartingChannel for the stream");
*reinterpret_cast(outData) = mStartingChannel;
outDataSize = sizeof(UInt32);
break;
case kAudioStreamPropertyLatency:
// This property returns any additional presentation latency the stream has.
ThrowIf(inDataSize < sizeof(UInt32),
CAException(kAudioHardwareBadPropertySizeError),
"VC_Stream::GetPropertyData: not enough space for the return "
"value of kAudioStreamPropertyLatency for the stream");
*reinterpret_cast(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),
"VC_Stream::GetPropertyData: not enough space for the return "
"value of kAudioStreamPropertyVirtualFormat for the stream");
// This particular device always vends 32-bit native endian floats
AudioStreamBasicDescription* outASBD =
reinterpret_cast(outData);
// Our streams have the same sample rate as the device they belong to.
outASBD->mSampleRate = mSampleRate;
outASBD->mFormatID = kAudioFormatLinearPCM;
outASBD->mFormatFlags =
kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
outASBD->mBytesPerPacket = 8;
outASBD->mFramesPerPacket = 1;
outASBD->mBytesPerFrame = 8;
outASBD->mChannelsPerFrame = 2;
outASBD->mBitsPerChannel = 32;
outDataSize = sizeof(AudioStreamBasicDescription);
}
break;
case kAudioStreamPropertyAvailableVirtualFormats:
case kAudioStreamPropertyAvailablePhysicalFormats:
// This returns an array of AudioStreamRangedDescriptions that describe what
// formats are supported.
if((inDataSize / sizeof(AudioStreamRangedDescription)) >= 1)
{
AudioStreamRangedDescription* outASRD =
reinterpret_cast(outData);
outASRD[0].mFormat.mSampleRate = mSampleRate;
outASRD[0].mFormat.mFormatID = kAudioFormatLinearPCM;
outASRD[0].mFormat.mFormatFlags =
kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
outASRD[0].mFormat.mBytesPerPacket = 8;
outASRD[0].mFormat.mFramesPerPacket = 1;
outASRD[0].mFormat.mBytesPerFrame = 8;
outASRD[0].mFormat.mChannelsPerFrame = 2;
outASRD[0].mFormat.mBitsPerChannel = 32;
// These match kAudioDevicePropertyAvailableNominalSampleRates.
outASRD[0].mSampleRateRange.mMinimum = 1.0;
outASRD[0].mSampleRateRange.mMaximum = 1000000000.0;
// Report how much we wrote.
outDataSize = sizeof(AudioStreamRangedDescription);
}
else
{
outDataSize = 0;
}
break;
default:
VC_Object::GetPropertyData(inObjectID,
inClientPID,
inAddress,
inQualifierDataSize,
inQualifierData,
inDataSize,
outDataSize,
outData);
break;
};
}
void VC_Stream::SetPropertyData(AudioObjectID inObjectID,
pid_t inClientPID,
const AudioObjectPropertyAddress& inAddress,
UInt32 inQualifierDataSize,
const void* __nullable inQualifierData,
UInt32 inDataSize,
const void* inData)
{
// There is more detailed commentary about each property in the 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),
"VC_Stream::SetPropertyData: wrong size for the data for "
"kAudioStreamPropertyIsActive");
bool theNewIsActive = *reinterpret_cast(inData) != 0;
CAMutex::Locker theStateLocker(mStateMutex);
if(mIsStreamActive != theNewIsActive)
{
mIsStreamActive = theNewIsActive;
// Send the notification.
CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{
AudioObjectPropertyAddress theProperty[] = {
CAPropertyAddress(kAudioStreamPropertyIsActive)
};
VC_PlugIn::Host_PropertiesChanged(inObjectID, 1, theProperty);
});
}
}
break;
case kAudioStreamPropertyVirtualFormat:
case kAudioStreamPropertyPhysicalFormat:
{
// The device that owns the stream handles changing the stream format, as it needs
// to be handled via the RequestConfigChange/PerformConfigChange machinery. The
// stream only needs to validate the format at this point.
//
// Note that because our devices only supports 2 channel 32 bit float data, the only
// thing that can change is the sample rate.
ThrowIf(inDataSize != sizeof(AudioStreamBasicDescription),
CAException(kAudioHardwareBadPropertySizeError),
"VC_Stream::SetPropertyData: wrong size for the data for "
"kAudioStreamPropertyPhysicalFormat");
const AudioStreamBasicDescription* theNewFormat =
reinterpret_cast(inData);
ThrowIf(theNewFormat->mFormatID != kAudioFormatLinearPCM,
CAException(kAudioDeviceUnsupportedFormatError),
"VC_Stream::SetPropertyData: unsupported format ID for "
"kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mFormatFlags !=
(kAudioFormatFlagIsFloat |
kAudioFormatFlagsNativeEndian |
kAudioFormatFlagIsPacked),
CAException(kAudioDeviceUnsupportedFormatError),
"VC_Stream::SetPropertyData: unsupported format flags for "
"kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mBytesPerPacket != 8,
CAException(kAudioDeviceUnsupportedFormatError),
"VC_Stream::SetPropertyData: unsupported bytes per packet for "
"kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mFramesPerPacket != 1,
CAException(kAudioDeviceUnsupportedFormatError),
"VC_Stream::SetPropertyData: unsupported frames per packet for "
"kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mBytesPerFrame != 8,
CAException(kAudioDeviceUnsupportedFormatError),
"VC_Stream::SetPropertyData: unsupported bytes per frame for "
"kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mChannelsPerFrame != 2,
CAException(kAudioDeviceUnsupportedFormatError),
"VC_Stream::SetPropertyData: unsupported channels per frame for "
"kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mBitsPerChannel != 32,
CAException(kAudioDeviceUnsupportedFormatError),
"VC_Stream::SetPropertyData: unsupported bits per channel for "
"kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mSampleRate < 1.0,
CAException(kAudioDeviceUnsupportedFormatError),
"VC_Stream::SetPropertyData: unsupported sample rate for "
"kAudioStreamPropertyPhysicalFormat");
}
break;
default:
VC_Object::SetPropertyData(inObjectID,
inClientPID,
inAddress,
inQualifierDataSize,
inQualifierData,
inDataSize,
inData);
break;
};
}
#pragma mark Accessors
void VC_Stream::SetSampleRate(Float64 inSampleRate)
{
CAMutex::Locker theStateLocker(mStateMutex);
mSampleRate = inSampleRate;
}
#pragma clang assume_nonnull end