mirror of
https://github.com/morgan9e/VolumeControl
synced 2026-04-14 00:04:05 +09:00
Initial commit
This commit is contained in:
488
driver/VC_Stream.cpp
Normal file
488
driver/VC_Stream.cpp
Normal file
@@ -0,0 +1,488 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// 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<AudioClassID*>(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<AudioClassID*>(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<AudioObjectID*>(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<UInt32*>(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<UInt32*>(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<UInt32*>(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<UInt32*>(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<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),
|
||||
"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<AudioStreamBasicDescription*>(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<AudioStreamRangedDescription*>(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<const UInt32*>(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<const AudioStreamBasicDescription*>(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
|
||||
|
||||
Reference in New Issue
Block a user