mirror of
https://github.com/kyleneideck/BackgroundMusic
synced 2024-11-10 06:34:22 +00:00
Ignore UI sounds when auto-pausing.
On macOS, apps are supposed to play UI-related sounds using the "system default" device. This commit creates a new instance of BGM_Driver, which BGMApp sets as the system default device. BGMApp ignores audio played to that device when deciding whether to pause/unpause the user's music player. Since UI sounds are short, this helps avoid pausing the music player and unpausing it shortly after.
This commit is contained in:
parent
a6e9179f2d
commit
b715212cab
39 changed files with 2311 additions and 1054 deletions
|
@ -244,7 +244,7 @@
|
|||
1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
|
||||
1CE7064A1BF1EC0600BFC06D /* BGMOutputDevicePrefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMOutputDevicePrefs.h; path = Preferences/BGMOutputDevicePrefs.h; sourceTree = "<group>"; };
|
||||
1CE7064B1BF1EC0600BFC06D /* BGMOutputDevicePrefs.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMOutputDevicePrefs.mm; path = Preferences/BGMOutputDevicePrefs.mm; sourceTree = "<group>"; };
|
||||
1CED61681C3081C2002CAFCF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
1CED61681C3081C2002CAFCF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
1CED616A1C316E1A002CAFCF /* BGMAudioDeviceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAudioDeviceManager.h; sourceTree = "<group>"; };
|
||||
1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAudioDeviceManager.mm; sourceTree = "<group>"; };
|
||||
1CF5423A1EAAEE4300445AD8 /* BGMAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMAudioDevice.cpp; sourceTree = "<group>"; };
|
||||
|
@ -272,7 +272,7 @@
|
|||
2743CA1C1D86DA9B0089613B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
275343BF1DFD01BC00DF3858 /* SystemPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SystemPreferences.h; sourceTree = "<group>"; };
|
||||
2769728B1CAFCEE8007A2F7C /* post_install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = post_install.sh; path = BGMXPCHelper/post_install.sh; sourceTree = SOURCE_ROOT; };
|
||||
2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */ = {isa = PBXFileReference; explicitFileType = text.xml; fileEncoding = 4; name = com.bearisdriving.BGM.XPCHelper.plist.template; path = BGMXPCHelper/com.bearisdriving.BGM.XPCHelper.plist.template; sourceTree = SOURCE_ROOT; };
|
||||
2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */ = {isa = PBXFileReference; explicitFileType = text.xml; fileEncoding = 1; name = com.bearisdriving.BGM.XPCHelper.plist.template; path = BGMXPCHelper/com.bearisdriving.BGM.XPCHelper.plist.template; sourceTree = SOURCE_ROOT; };
|
||||
276972901CB16008007A2F7C /* safe_install_dir.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = safe_install_dir.sh; path = BGMXPCHelper/safe_install_dir.sh; sourceTree = SOURCE_ROOT; };
|
||||
2771700F1CA0C83B00AB34B4 /* BGM_Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_Utils.h; path = ../SharedSource/BGM_Utils.h; sourceTree = "<group>"; };
|
||||
277170141CA24D7C00AB34B4 /* BGMXPCListenerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMXPCListenerDelegate.h; path = BGMXPCHelper/BGMXPCListenerDelegate.h; sourceTree = SOURCE_ROOT; };
|
||||
|
|
|
@ -75,6 +75,12 @@ static CGFloat const kAppVolumeViewInitialHeight = 20;
|
|||
[[NSWorkspace sharedWorkspace] removeObserver:self forKeyPath:@"runningApplications" context:nil];
|
||||
}
|
||||
|
||||
// This method allows the Interface Builder Custom Classes for controls (below) to send their values
|
||||
// directly to BGMDevice. Not public to other classes.
|
||||
- (BGMAudioDeviceManager*) audioDevices {
|
||||
return audioDevices;
|
||||
}
|
||||
|
||||
#pragma mark UI Modifications
|
||||
|
||||
- (void) insertMenuItemsForApps:(NSArray<NSRunningApplication*>*)apps {
|
||||
|
@ -176,7 +182,7 @@ static CGFloat const kAppVolumeViewInitialHeight = 20;
|
|||
}
|
||||
}
|
||||
|
||||
- (void) setVolumeOfMenuItem:(NSMenuItem*)menuItem fromAppVolumes:(CACFArray&)appVolumes {
|
||||
- (void) setVolumeOfMenuItem:(NSMenuItem*)menuItem fromAppVolumes:(const CACFArray&)appVolumes {
|
||||
// Set menuItem's volume slider to the volume of the app in appVolumes that menuItem represents
|
||||
// Leaves menuItem unchanged if it doesn't match any of the apps in appVolumes
|
||||
NSRunningApplication* representedApp = menuItem.representedObject;
|
||||
|
@ -269,8 +275,8 @@ static CGFloat const kAppVolumeViewInitialHeight = 20;
|
|||
|
||||
// KVO callback for the apps currently running on the system. Adds/removes the associated menu items.
|
||||
if ([keyPath isEqualToString:@"runningApplications"]) {
|
||||
NSArray<NSRunningApplication*>* newApps = [change objectForKey:NSKeyValueChangeNewKey];
|
||||
NSArray<NSRunningApplication*>* oldApps = [change objectForKey:NSKeyValueChangeOldKey];
|
||||
NSArray<NSRunningApplication*>* newApps = change[NSKeyValueChangeNewKey];
|
||||
NSArray<NSRunningApplication*>* oldApps = change[NSKeyValueChangeOldKey];
|
||||
|
||||
int changeKind = [[change valueForKey:NSKeyValueChangeKindKey] intValue];
|
||||
switch (changeKind) {
|
||||
|
@ -295,70 +301,6 @@ static CGFloat const kAppVolumeViewInitialHeight = 20;
|
|||
}
|
||||
}
|
||||
|
||||
#pragma mark BGMDevice Communication
|
||||
|
||||
- (void) sendVolumeChangeToBGMDevice:(SInt32)newVolume appProcessID:(pid_t)appProcessID appBundleID:(NSString*)appBundleID {
|
||||
CACFDictionary appVolumeChange(true);
|
||||
appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), appProcessID);
|
||||
appVolumeChange.AddString(CFSTR(kBGMAppVolumesKey_BundleID), (__bridge CFStringRef)appBundleID);
|
||||
// The values from our sliders are in [kAppRelativeVolumeMinRawValue, kAppRelativeVolumeMaxRawValue] already
|
||||
appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), newVolume);
|
||||
|
||||
CACFArray appVolumeChanges(true);
|
||||
appVolumeChanges.AppendDictionary(appVolumeChange.GetDict());
|
||||
|
||||
[audioDevices bgmDevice].SetPropertyData_CFType(kBGMAppVolumesAddress, appVolumeChanges.AsPropertyList());
|
||||
}
|
||||
|
||||
- (void) sendPanPositionChangeToBGMDevice:(SInt32)newPanPosition appProcessID:(pid_t)appProcessID appBundleID:(NSString*)appBundleID {
|
||||
CACFDictionary appVolumeChange(true);
|
||||
appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), appProcessID);
|
||||
appVolumeChange.AddString(CFSTR(kBGMAppVolumesKey_BundleID), (__bridge CFStringRef)appBundleID);
|
||||
|
||||
// The values from our sliders are in [kAppPanLeftRawValue, kAppPanRightRawValue] already
|
||||
appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), newPanPosition);
|
||||
|
||||
CACFArray appVolumeChanges(true);
|
||||
appVolumeChanges.AppendDictionary(appVolumeChange.GetDict());
|
||||
|
||||
[audioDevices bgmDevice].SetPropertyData_CFType(kBGMAppVolumesAddress, appVolumeChanges.AsPropertyList());
|
||||
}
|
||||
|
||||
// This is a temporary solution that lets us control the volumes of some multiprocess apps, i.e.
|
||||
// apps that play their audio from a process with a different bundle ID.
|
||||
//
|
||||
// We can't just check the child processes of the apps' main processes because they're usually
|
||||
// created with launchd rather than being actual child processes. There's a private API to get the
|
||||
// processes that an app is "responsible for", so we'll try to use it in the proper fix and only use
|
||||
// this list if the API doesn't work.
|
||||
//
|
||||
// TODO: Consider moving the logic to a new class when we fix this issue properly so this class is
|
||||
// only responsible for UI.
|
||||
+ (NSArray<NSString*>*) responsibleBundleIDsOf:(NSString*)parentBundleID {
|
||||
NSDictionary<NSString*, NSArray<NSString*>*>* bundleIDMap = @{
|
||||
// Safari
|
||||
@"com.apple.Safari": @[@"com.apple.WebKit.WebContent"],
|
||||
// Firefox
|
||||
@"org.mozilla.firefox": @[@"org.mozilla.plugincontainer"],
|
||||
// Firefox Nightly
|
||||
@"org.mozilla.nightly": @[@"org.mozilla.plugincontainer"],
|
||||
// VMWare Fusion
|
||||
@"com.vmware.fusion": @[@"com.vmware.vmware-vmx"],
|
||||
// Parallels
|
||||
@"com.parallels.desktop.console": @[@"com.parallels.vm"],
|
||||
// MPlayer OSX Extended
|
||||
@"hu.mplayerhq.mplayerosx.extended": @[@"ch.sttz.mplayerosx.extended.binaries.officialsvn"]
|
||||
};
|
||||
|
||||
// Parallels' VM "dock helper" apps have bundle IDs like
|
||||
// com.parallels.winapp.87f6bfc236d64d70a81c47f6243add4c.f5a25fdede514f7aa0a475a1873d3287.fs
|
||||
if ([parentBundleID hasPrefix:@"com.parallels.winapp."]) {
|
||||
return @[@"com.parallels.vm"];
|
||||
}
|
||||
|
||||
return bundleIDMap[parentBundleID];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark Custom Classes (IB)
|
||||
|
@ -464,11 +406,11 @@ static CGFloat const kAppVolumeViewInitialHeight = 20;
|
|||
|
||||
[self snap];
|
||||
|
||||
[context sendVolumeChangeToBGMDevice:self.intValue appProcessID:appProcessID appBundleID:appBundleID];
|
||||
|
||||
for (NSString* bundleID : [BGMAppVolumes responsibleBundleIDsOf:appBundleID]) {
|
||||
[context sendVolumeChangeToBGMDevice:self.intValue appProcessID:-1 appBundleID:bundleID];
|
||||
}
|
||||
// The values from our sliders are in
|
||||
// [kAppRelativeVolumeMinRawValue, kAppRelativeVolumeMaxRawValue] already.
|
||||
[context.audioDevices sendAppVolumeToBGMDevice:self.intValue
|
||||
appProcessID:appProcessID
|
||||
appBundleID:appBundleID];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -511,11 +453,10 @@ static CGFloat const kAppVolumeViewInitialHeight = 20;
|
|||
|
||||
DebugMsg("BGMAppVolumes::appPanPositionChanged: App pan position for %s changed to %d", appBundleID.UTF8String, self.intValue);
|
||||
|
||||
[context sendPanPositionChangeToBGMDevice:self.intValue appProcessID:appProcessID appBundleID:appBundleID];
|
||||
|
||||
for (NSString* bundleID : [BGMAppVolumes responsibleBundleIDsOf:appBundleID]) {
|
||||
[context sendPanPositionChangeToBGMDevice:self.intValue appProcessID:-1 appBundleID:bundleID];
|
||||
}
|
||||
// The values from our sliders are in [kAppPanLeftRawValue, kAppPanRightRawValue] already.
|
||||
[context.audioDevices sendAppPanPositionToBGMDevice:self.intValue
|
||||
appProcessID:appProcessID
|
||||
appBundleID:appBundleID];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -61,14 +61,17 @@ BGMAudioDevice::~BGMAudioDevice()
|
|||
bool BGMAudioDevice::CanBeOutputDeviceInBGMApp() const
|
||||
{
|
||||
CFStringRef uid = CopyDeviceUID();
|
||||
bool isBGMDevice = CFEqual(uid, CFSTR(kBGMDeviceUID));
|
||||
bool isNullDevice = CFEqual(uid, CFSTR(kBGMNullDeviceUID));
|
||||
CFRelease(uid);
|
||||
|
||||
bool hasOutputChannels = GetTotalNumberChannels(/* inIsInput = */ false) > 0;
|
||||
bool canBeDefault = CanBeDefaultDevice(/* inIsInput = */ false, /* inIsSystem = */ false);
|
||||
|
||||
return !isBGMDevice && !isNullDevice && !IsHidden() && hasOutputChannels && canBeDefault;
|
||||
return !IsBGMDeviceInstance() &&
|
||||
!isNullDevice &&
|
||||
!IsHidden() &&
|
||||
hasOutputChannels &&
|
||||
canBeDefault;
|
||||
}
|
||||
|
||||
#pragma mark Available Controls
|
||||
|
@ -331,6 +334,27 @@ bool BGMAudioDevice::GetVirtualMasterBalance(AudioObjectPropertyScope inScope
|
|||
&outVirtualMasterBalance);
|
||||
}
|
||||
|
||||
#pragma mark Implementation
|
||||
|
||||
bool BGMAudioDevice::IsBGMDevice(bool inIncludeUISoundsInstance) const
|
||||
{
|
||||
bool isBGMDevice = false;
|
||||
|
||||
if(GetObjectID() != kAudioObjectUnknown)
|
||||
{
|
||||
// Check the device's UID to see whether it's BGMDevice.
|
||||
CFStringRef uid = CopyDeviceUID();
|
||||
|
||||
isBGMDevice =
|
||||
CFEqual(uid, CFSTR(kBGMDeviceUID)) ||
|
||||
(inIncludeUISoundsInstance && CFEqual(uid, CFSTR(kBGMDeviceUID_UISounds)));
|
||||
|
||||
CFRelease(uid);
|
||||
}
|
||||
|
||||
return isBGMDevice;
|
||||
}
|
||||
|
||||
// static
|
||||
OSStatus BGMAudioDevice::AHSGetPropertyData(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress* inAddress,
|
||||
|
|
|
@ -51,7 +51,23 @@ public:
|
|||
|
||||
operator AudioObjectID() const { return GetObjectID(); }
|
||||
|
||||
/*! @throws CAException */
|
||||
/*!
|
||||
@return True if this device is BGMDevice. (Specifically, the main instance of BGMDevice.)
|
||||
@throws CAException If the HAL returns an error when queried.
|
||||
*/
|
||||
bool IsBGMDevice() const { return IsBGMDevice(false); };
|
||||
/*!
|
||||
@return True if this device is either the main instance of BGMDevice (the device named
|
||||
"Background Music") or the instance used for UI sounds (the device named "Background
|
||||
Music (UI Sounds)").
|
||||
@throws CAException If the HAL returns an error when queried.
|
||||
*/
|
||||
bool IsBGMDeviceInstance() const { return IsBGMDevice(true); };
|
||||
|
||||
/*!
|
||||
@return True if this device can be set as the output device in BGMApp.
|
||||
@throws CAException If the HAL returns an error when queried.
|
||||
*/
|
||||
bool CanBeOutputDeviceInBGMApp() const;
|
||||
|
||||
#pragma mark Available Controls
|
||||
|
@ -77,7 +93,11 @@ public:
|
|||
bool GetVirtualMasterBalance(AudioObjectPropertyScope inScope,
|
||||
Float32& outVirtualMasterBalance) const;
|
||||
|
||||
#pragma mark Implementation
|
||||
|
||||
private:
|
||||
bool IsBGMDevice(bool inIncludingUISoundsInstance) const;
|
||||
|
||||
static OSStatus AHSGetPropertyData(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress* inAddress,
|
||||
UInt32* ioDataSize,
|
||||
|
|
|
@ -17,11 +17,10 @@
|
|||
// BGMAudioDeviceManager.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016 Kyle Neideck
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
//
|
||||
// Manages the BGMDevice and the output device. Sets the system's current default device as the
|
||||
// output device on init, then starts playthrough and mirroring the devices' controls. The output
|
||||
// device can be changed but the BGMDevice is fixed.
|
||||
// Manages BGMDevice and the output device. Sets the system's current default device as the output
|
||||
// device on init, then starts playthrough and mirroring the devices' controls.
|
||||
//
|
||||
|
||||
// PublicUtility Includes
|
||||
|
@ -61,6 +60,13 @@ const int kBGMErrorCode_ReturningEarly = 3;
|
|||
- (BOOL) isOutputDevice:(AudioObjectID)deviceID;
|
||||
- (BOOL) isOutputDataSource:(UInt32)dataSourceID;
|
||||
|
||||
- (void) sendAppVolumeToBGMDevice:(SInt32)newVolume
|
||||
appProcessID:(pid_t)appProcessID
|
||||
appBundleID:(NSString*)appBundleID;
|
||||
- (void) sendAppPanPositionToBGMDevice:(SInt32)newPanPosition
|
||||
appProcessID:(pid_t)appProcessID
|
||||
appBundleID:(NSString*)appBundleID;
|
||||
|
||||
// Set the audio output device that BGMApp uses.
|
||||
//
|
||||
// Returns an error if the output device couldn't be changed. If revertOnFailure is true in that case,
|
||||
|
@ -85,7 +91,7 @@ const int kBGMErrorCode_ReturningEarly = 3;
|
|||
//
|
||||
// Returns one of the error codes defined by this class or BGMPlayThrough, or an AudioHardware error
|
||||
// code received from the HAL.
|
||||
- (OSStatus) startPlayThroughSync;
|
||||
- (OSStatus) startPlayThroughSync:(BOOL)forUISoundsDevice;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
// Copyright © 2017 Andrew Tonner
|
||||
//
|
||||
|
||||
// Self Include
|
||||
|
@ -33,14 +34,18 @@
|
|||
// PublicUtility Includes
|
||||
#include "CAHALAudioSystemObject.h"
|
||||
#include "CAAutoDisposer.h"
|
||||
#include "CACFDictionary.h"
|
||||
#include "CACFArray.h"
|
||||
|
||||
|
||||
@implementation BGMAudioDeviceManager {
|
||||
BGMAudioDevice bgmDevice;
|
||||
BGMAudioDevice bgmDevice_UISounds;
|
||||
BGMAudioDevice outputDevice;
|
||||
|
||||
BGMDeviceControlSync deviceControlSync;
|
||||
BGMPlayThrough playThrough;
|
||||
BGMPlayThrough playThrough_UISounds;
|
||||
|
||||
NSRecursiveLock* stateLock;
|
||||
}
|
||||
|
@ -52,8 +57,9 @@
|
|||
stateLock = [NSRecursiveLock new];
|
||||
|
||||
bgmDevice = BGMAudioDevice(CFSTR(kBGMDeviceUID));
|
||||
bgmDevice_UISounds = BGMAudioDevice(CFSTR(kBGMDeviceUID_UISounds));
|
||||
|
||||
if (bgmDevice.GetObjectID() == kAudioObjectUnknown) {
|
||||
if ((bgmDevice == kAudioObjectUnknown) || (bgmDevice_UISounds == kAudioObjectUnknown)) {
|
||||
LogError("BGMAudioDeviceManager::initWithError: BGMDevice not found");
|
||||
|
||||
if (error) {
|
||||
|
@ -63,8 +69,14 @@
|
|||
self = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
[self initOutputDevice];
|
||||
|
||||
try {
|
||||
[self initOutputDevice];
|
||||
} catch (const CAException& e) {
|
||||
LogError("BGMAudioDeviceManager::initWithError: failed to init output device (%u)",
|
||||
e.GetError());
|
||||
outputDevice.SetObjectID(kAudioObjectUnknown);
|
||||
}
|
||||
|
||||
if (outputDevice.GetObjectID() == kAudioObjectUnknown) {
|
||||
LogError("BGMAudioDeviceManager::initWithError: output device not found");
|
||||
|
@ -81,64 +93,83 @@
|
|||
return self;
|
||||
}
|
||||
|
||||
// Throws a CAException if it fails to set the output device.
|
||||
- (void) initOutputDevice {
|
||||
CAHALAudioSystemObject audioSystem;
|
||||
// outputDevice = BGMAudioDevice(CFSTR("AppleHDAEngineOutput:1B,0,1,1:0"));
|
||||
AudioObjectID defaultDeviceID = audioSystem.GetDefaultAudioDevice(false, false);
|
||||
BGMAudioDevice defaultDevice = audioSystem.GetDefaultAudioDevice(false, false);
|
||||
|
||||
if (defaultDeviceID == bgmDevice.GetObjectID()) {
|
||||
// TODO: If BGMDevice is already the default (because BGMApp didn't shutdown properly or it was set manually)
|
||||
// we should temporarily disable BGMDevice so we can find out what the previous default was.
|
||||
|
||||
// For now, just pick the device with the lowest latency
|
||||
UInt32 numDevices = audioSystem.GetNumberAudioDevices();
|
||||
if (numDevices > 0) {
|
||||
SInt32 minLatencyDeviceIdx = -1;
|
||||
UInt32 minLatency = UINT32_MAX;
|
||||
|
||||
CAAutoArrayDelete<AudioObjectID> devices(numDevices);
|
||||
audioSystem.GetAudioDevices(numDevices, devices);
|
||||
|
||||
for (UInt32 i = 0; i < numDevices; i++) {
|
||||
BGMAudioDevice device(devices[i]);
|
||||
|
||||
BOOL isBGMDevice = device.GetObjectID() == bgmDevice.GetObjectID();
|
||||
BOOL hasOutputChannels = device.GetTotalNumberChannels(/* inIsInput = */ false) > 0;
|
||||
|
||||
if (!isBGMDevice && hasOutputChannels) {
|
||||
if (minLatencyDeviceIdx == -1) {
|
||||
// First, look for any device other than BGMDevice
|
||||
minLatencyDeviceIdx = i;
|
||||
} else if (device.GetLatency(false) < minLatency) {
|
||||
// Then compare the devices by their latencies
|
||||
minLatencyDeviceIdx = i;
|
||||
minLatency = device.GetLatency(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BGMLogUnexpectedExceptionsMsg("BGMAudioDeviceManager::initOutputDevice",
|
||||
"setOutputDeviceWithID:devices[minLatencyDeviceIdx]", [&]() {
|
||||
// TODO: On error, try a different output device.
|
||||
[self setOutputDeviceWithID:devices[minLatencyDeviceIdx] revertOnFailure:NO];
|
||||
});
|
||||
}
|
||||
if (defaultDevice.IsBGMDeviceInstance()) {
|
||||
// BGMDevice is already the default (it could have been set manually or BGMApp could have
|
||||
// failed to change it back the last time it closed), so just pick the device with the
|
||||
// lowest latency.
|
||||
//
|
||||
// TODO: Temporarily disable BGMDevice so we can find out what the previous default was and
|
||||
// use that instead.
|
||||
[self setOutputDeviceByLatency];
|
||||
} else {
|
||||
BGMLogUnexpectedExceptionsMsg("BGMAudioDeviceManager::initOutputDevice",
|
||||
"setOutputDeviceWithID:defaultDeviceID", [&]() {
|
||||
// TODO: Return the error from setOutputDeviceWithID so it can be returned by initWithError.
|
||||
[self setOutputDeviceWithID:defaultDeviceID revertOnFailure:NO];
|
||||
});
|
||||
// TODO: Return the error from setOutputDeviceWithID so it can be returned by initWithError.
|
||||
[self setOutputDeviceWithID:defaultDevice revertOnFailure:NO];
|
||||
}
|
||||
|
||||
if (outputDevice == kAudioObjectUnknown) {
|
||||
LogError("BGMAudioDeviceManager::initOutputDevice: Failed to set output device");
|
||||
Throw(CAException(kAudioHardwareUnspecifiedError));
|
||||
}
|
||||
|
||||
if (outputDevice.IsBGMDeviceInstance()) {
|
||||
LogError("BGMAudioDeviceManager::initOutputDevice: Failed to change output device from "
|
||||
"BGMDevice");
|
||||
Throw(CAException(kAudioHardwareUnspecifiedError));
|
||||
}
|
||||
|
||||
assert(outputDevice.GetObjectID() != bgmDevice.GetObjectID());
|
||||
|
||||
// Log message
|
||||
if (outputDevice.GetObjectID() == kAudioObjectUnknown) {
|
||||
CFStringRef outputDeviceUID = outputDevice.CopyDeviceUID();
|
||||
DebugMsg("BGMAudioDeviceManager::initDevices: Set output device to %s",
|
||||
CFStringGetCStringPtr(outputDeviceUID, kCFStringEncodingUTF8));
|
||||
CFRelease(outputDeviceUID);
|
||||
CFStringRef outputDeviceUID = outputDevice.CopyDeviceUID();
|
||||
DebugMsg("BGMAudioDeviceManager::initOutputDevice: Set output device to %s",
|
||||
CFStringGetCStringPtr(outputDeviceUID, kCFStringEncodingUTF8));
|
||||
CFRelease(outputDeviceUID);
|
||||
}
|
||||
|
||||
- (void) setOutputDeviceByLatency {
|
||||
CAHALAudioSystemObject audioSystem;
|
||||
UInt32 numDevices = audioSystem.GetNumberAudioDevices();
|
||||
|
||||
if (numDevices > 0) {
|
||||
BGMAudioDevice minLatencyDevice = kAudioObjectUnknown;
|
||||
UInt32 minLatency = UINT32_MAX;
|
||||
|
||||
CAAutoArrayDelete<AudioObjectID> devices(numDevices);
|
||||
audioSystem.GetAudioDevices(numDevices, devices);
|
||||
|
||||
for (UInt32 i = 0; i < numDevices; i++) {
|
||||
BGMAudioDevice device(devices[i]);
|
||||
|
||||
if (!device.IsBGMDeviceInstance()) {
|
||||
BOOL hasOutputChannels = NO;
|
||||
|
||||
BGMLogAndSwallowExceptionsMsg("BGMAudioDeviceManager::setOutputDeviceByLatency",
|
||||
"GetTotalNumberChannels", ([&] {
|
||||
hasOutputChannels = device.GetTotalNumberChannels(/* inIsInput = */ false) > 0;
|
||||
}));
|
||||
|
||||
if (hasOutputChannels) {
|
||||
BGMLogAndSwallowExceptionsMsg("BGMAudioDeviceManager::setOutputDeviceByLatency",
|
||||
"GetLatency", ([&] {
|
||||
UInt32 latency = device.GetLatency(false);
|
||||
|
||||
if (latency < minLatency) {
|
||||
minLatencyDevice = devices[i];
|
||||
minLatency = latency;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (minLatencyDevice != kAudioObjectUnknown) {
|
||||
// TODO: On error, try a different output device.
|
||||
[self setOutputDeviceWithID:minLatencyDevice revertOnFailure:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,14 +183,17 @@
|
|||
"device to BGMDevice");
|
||||
|
||||
CAHALAudioSystemObject audioSystem;
|
||||
|
||||
|
||||
// Copy the device IDs so we can call the HAL without holding stateLock. See startPlayThroughSync.
|
||||
AudioDeviceID bgmDeviceID = kAudioObjectUnknown;
|
||||
AudioDeviceID bgmDeviceUISoundsID = kAudioObjectUnknown;
|
||||
AudioDeviceID outputDeviceID = kAudioObjectUnknown;
|
||||
|
||||
@try {
|
||||
[stateLock lock];
|
||||
|
||||
bgmDeviceID = bgmDevice.GetObjectID();
|
||||
bgmDeviceUISoundsID = bgmDevice_UISounds.GetObjectID();
|
||||
outputDeviceID = outputDevice.GetObjectID();
|
||||
} @finally {
|
||||
[stateLock unlock];
|
||||
|
@ -168,7 +202,8 @@
|
|||
if (outputDeviceID == kAudioObjectUnknown) {
|
||||
return [NSError errorWithDomain:@kBGMAppBundleID code:kBGMErrorCode_OutputDeviceNotFound userInfo:nil];
|
||||
}
|
||||
if (bgmDeviceID == kAudioObjectUnknown) {
|
||||
|
||||
if ((bgmDeviceID == kAudioObjectUnknown) || (bgmDeviceUISoundsID == kAudioObjectUnknown)) {
|
||||
return [NSError errorWithDomain:@kBGMAppBundleID code:kBGMErrorCode_BGMDeviceNotFound userInfo:nil];
|
||||
}
|
||||
|
||||
|
@ -178,7 +213,11 @@
|
|||
try {
|
||||
if (currentDefault == outputDeviceID) {
|
||||
// The default system device was the same as the default device, so change that as well
|
||||
audioSystem.SetDefaultAudioDevice(false, true, bgmDeviceID);
|
||||
//
|
||||
// Use the UI sounds instance of BGMDevice because the default system output device
|
||||
// is the device "to use for system related sound". The allows BGMDriver to tell
|
||||
// when the audio it receives is UI-related.
|
||||
audioSystem.SetDefaultAudioDevice(false, true, bgmDeviceUISoundsID);
|
||||
}
|
||||
|
||||
audioSystem.SetDefaultAudioDevice(false, false, bgmDeviceID);
|
||||
|
@ -199,22 +238,25 @@
|
|||
|
||||
bool bgmDeviceIsDefault = true;
|
||||
bool bgmDeviceIsSystemDefault = true;
|
||||
|
||||
|
||||
// Copy the device IDs so we can call the HAL without holding stateLock. See startPlayThroughSync.
|
||||
AudioDeviceID bgmDeviceID = kAudioObjectUnknown;
|
||||
AudioDeviceID bgmDeviceUISoundsID = kAudioObjectUnknown;
|
||||
AudioDeviceID outputDeviceID = kAudioObjectUnknown;
|
||||
|
||||
@try {
|
||||
[stateLock lock];
|
||||
|
||||
bgmDeviceID = bgmDevice.GetObjectID();
|
||||
bgmDeviceUISoundsID = bgmDevice_UISounds.GetObjectID();
|
||||
outputDeviceID = outputDevice.GetObjectID();
|
||||
|
||||
BGMLogAndSwallowExceptions("unsetBGMDeviceAsOSDefault", [&]() {
|
||||
BGMLogAndSwallowExceptions("unsetBGMDeviceAsOSDefault", [&] {
|
||||
bgmDeviceIsDefault =
|
||||
(audioSystem.GetDefaultAudioDevice(false, false) == bgmDeviceID);
|
||||
|
||||
bgmDeviceIsSystemDefault =
|
||||
(audioSystem.GetDefaultAudioDevice(false, true) == bgmDeviceID);
|
||||
(audioSystem.GetDefaultAudioDevice(false, true) == bgmDeviceUISoundsID);
|
||||
});
|
||||
} @finally {
|
||||
[stateLock unlock];
|
||||
|
@ -280,6 +322,8 @@
|
|||
}
|
||||
|
||||
- (BOOL) isOutputDataSource:(UInt32)dataSourceID {
|
||||
BOOL isOutputDataSource = NO;
|
||||
|
||||
@try {
|
||||
[stateLock lock];
|
||||
|
||||
|
@ -287,17 +331,107 @@
|
|||
AudioObjectPropertyScope scope = kAudioDevicePropertyScopeOutput;
|
||||
UInt32 channel = 0;
|
||||
|
||||
return outputDevice.HasDataSourceControl(scope, channel) &&
|
||||
(dataSourceID == outputDevice.GetCurrentDataSourceID(scope, channel));
|
||||
isOutputDataSource =
|
||||
outputDevice.HasDataSourceControl(scope, channel) &&
|
||||
(dataSourceID == outputDevice.GetCurrentDataSourceID(scope, channel));
|
||||
} catch (CAException e) {
|
||||
BGMLogException(e);
|
||||
return false;
|
||||
}
|
||||
} @finally {
|
||||
[stateLock unlock];
|
||||
}
|
||||
|
||||
return isOutputDataSource;
|
||||
}
|
||||
|
||||
#pragma mark App Volumes
|
||||
|
||||
- (void) sendAppVolumeToBGMDevice:(SInt32)newVolume
|
||||
appProcessID:(pid_t)appProcessID
|
||||
appBundleID:(NSString*)appBundleID {
|
||||
[self sendAppVolumePanToBGMDevice:newVolume
|
||||
volumeTypeKey:CFSTR(kBGMAppVolumesKey_RelativeVolume)
|
||||
appProcessID:appProcessID
|
||||
appBundleID:appBundleID];
|
||||
}
|
||||
|
||||
- (void) sendAppPanPositionToBGMDevice:(SInt32)newPanPosition
|
||||
appProcessID:(pid_t)appProcessID
|
||||
appBundleID:(NSString*)appBundleID {
|
||||
[self sendAppVolumePanToBGMDevice:newPanPosition
|
||||
volumeTypeKey:CFSTR(kBGMAppVolumesKey_PanPosition)
|
||||
appProcessID:appProcessID
|
||||
appBundleID:appBundleID];
|
||||
}
|
||||
|
||||
- (void) sendAppVolumePanToBGMDevice:(SInt32)newValue
|
||||
volumeTypeKey:(CFStringRef)typeKey
|
||||
appProcessID:(pid_t)appProcessID
|
||||
appBundleID:(NSString*)appBundleID {
|
||||
CACFArray appVolumeChanges(true);
|
||||
|
||||
auto addVolumeChange = [&] (pid_t pid, NSString* bundleID) {
|
||||
CACFDictionary appVolumeChange(true);
|
||||
|
||||
appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), pid);
|
||||
appVolumeChange.AddString(CFSTR(kBGMAppVolumesKey_BundleID),
|
||||
(__bridge CFStringRef)bundleID);
|
||||
appVolumeChange.AddSInt32(typeKey, newValue);
|
||||
|
||||
appVolumeChanges.AppendDictionary(appVolumeChange.GetDict());
|
||||
};
|
||||
|
||||
addVolumeChange(appProcessID, appBundleID);
|
||||
|
||||
// Add the same change for each process the app is responsible for.
|
||||
for (NSString* responsibleBundleID :
|
||||
[BGMAudioDeviceManager responsibleBundleIDsOf:appBundleID]) {
|
||||
// Send -1 as the PID so this volume will only ever be matched by bundle ID.
|
||||
addVolumeChange(-1, responsibleBundleID);
|
||||
}
|
||||
|
||||
CFPropertyListRef changesPList = appVolumeChanges.AsPropertyList();
|
||||
|
||||
// Send the change to BGMDevice.
|
||||
bgmDevice.SetPropertyData_CFType(kBGMAppVolumesAddress, changesPList);
|
||||
|
||||
// Also send it to the instance of BGMDevice that handles UI sounds.
|
||||
bgmDevice_UISounds.SetPropertyData_CFType(kBGMAppVolumesAddress, changesPList);
|
||||
}
|
||||
|
||||
// This is a temporary solution that lets us control the volumes of some multiprocess apps, i.e.
|
||||
// apps that play their audio from a process with a different bundle ID.
|
||||
//
|
||||
// We can't just check the child processes of the apps' main processes because they're usually
|
||||
// created with launchd rather than being actual child processes. There's a private API to get the
|
||||
// processes that an app is "responsible for", so we'll try to use it in the proper fix and only use
|
||||
// this list if the API doesn't work.
|
||||
+ (NSArray<NSString*>*) responsibleBundleIDsOf:(NSString*)parentBundleID {
|
||||
NSDictionary<NSString*, NSArray<NSString*>*>* bundleIDMap = @{
|
||||
// Safari
|
||||
@"com.apple.Safari": @[@"com.apple.WebKit.WebContent"],
|
||||
// Firefox
|
||||
@"org.mozilla.firefox": @[@"org.mozilla.plugincontainer"],
|
||||
// Firefox Nightly
|
||||
@"org.mozilla.nightly": @[@"org.mozilla.plugincontainer"],
|
||||
// VMWare Fusion
|
||||
@"com.vmware.fusion": @[@"com.vmware.vmware-vmx"],
|
||||
// Parallels
|
||||
@"com.parallels.desktop.console": @[@"com.parallels.vm"],
|
||||
// MPlayer OSX Extended
|
||||
@"hu.mplayerhq.mplayerosx.extended": @[@"ch.sttz.mplayerosx.extended.binaries.officialsvn"]
|
||||
};
|
||||
|
||||
// Parallels' VM "dock helper" apps have bundle IDs like
|
||||
// com.parallels.winapp.87f6bfc236d64d70a81c47f6243add4c.f5a25fdede514f7aa0a475a1873d3287.fs
|
||||
if ([parentBundleID hasPrefix:@"com.parallels.winapp."]) {
|
||||
return @[@"com.parallels.vm"];
|
||||
}
|
||||
|
||||
return bundleIDMap[parentBundleID];
|
||||
}
|
||||
|
||||
#pragma mark Output Device
|
||||
|
||||
- (NSError* __nullable) setOutputDeviceWithID:(AudioObjectID)deviceID
|
||||
revertOnFailure:(BOOL)revertOnFailure {
|
||||
|
@ -317,7 +451,7 @@
|
|||
- (NSError* __nullable) setOutputDeviceWithIDImpl:(AudioObjectID)newDeviceID
|
||||
dataSourceID:(UInt32* __nullable)dataSourceID
|
||||
revertOnFailure:(BOOL)revertOnFailure {
|
||||
DebugMsg("BGMAudioDeviceManager::setOutputDeviceWithID: Setting output device. newDeviceID=%u",
|
||||
DebugMsg("BGMAudioDeviceManager::setOutputDeviceWithIDImpl: Setting output device. newDeviceID=%u",
|
||||
newDeviceID);
|
||||
|
||||
AudioDeviceID currentDeviceID = outputDevice.GetObjectID(); // (GetObjectID doesn't throw.)
|
||||
|
@ -337,6 +471,7 @@
|
|||
// Deactivate playthrough rather than stopping it so it can't be started by HAL
|
||||
// notifications while we're updating deviceControlSync.
|
||||
playThrough.Deactivate();
|
||||
playThrough_UISounds.Deactivate();
|
||||
|
||||
deviceControlSync.SetDevices(bgmDevice, newOutputDevice);
|
||||
deviceControlSync.Activate();
|
||||
|
@ -346,6 +481,11 @@
|
|||
playThrough.SetDevices(&bgmDevice, &newOutputDevice);
|
||||
playThrough.Activate();
|
||||
|
||||
// TODO: Support setting different devices as the default output device and the
|
||||
// default system output device, the way OS X does.
|
||||
playThrough_UISounds.SetDevices(&bgmDevice_UISounds, &newOutputDevice);
|
||||
playThrough_UISounds.Activate();
|
||||
|
||||
outputDevice = newOutputDevice;
|
||||
}
|
||||
|
||||
|
@ -361,8 +501,10 @@
|
|||
// playing. (If we only changed the data source, playthrough will already be running if it
|
||||
// needs to be.)
|
||||
playThrough.Start();
|
||||
playThrough_UISounds.Start();
|
||||
// But stop playthrough if audio isn't playing, since it uses CPU.
|
||||
playThrough.StopIfIdle();
|
||||
playThrough_UISounds.StopIfIdle();
|
||||
}
|
||||
} catch (CAException e) {
|
||||
BGMAssert(e.GetError() != kAudioHardwareNoError,
|
||||
|
@ -426,7 +568,7 @@
|
|||
return [NSError errorWithDomain:@kBGMAppBundleID code:errorCode userInfo:info];
|
||||
}
|
||||
|
||||
- (OSStatus) startPlayThroughSync {
|
||||
- (OSStatus) startPlayThroughSync:(BOOL)forUISoundsDevice {
|
||||
// We can only try for stateLock because setOutputDeviceWithID might have already taken it, then made a
|
||||
// HAL request to BGMDevice and be waiting for the response. Some of the requests setOutputDeviceWithID
|
||||
// makes to BGMDevice block in the HAL if another thread is in BGM_Device::StartIO.
|
||||
|
@ -442,6 +584,8 @@
|
|||
gotLock = [stateLock tryLock];
|
||||
|
||||
if (gotLock) {
|
||||
BGMPlayThrough& pt = (forUISoundsDevice ? playThrough_UISounds : playThrough);
|
||||
|
||||
// Playthrough might not have been notified that BGMDevice is starting yet, so make sure
|
||||
// playthrough is starting. This way we won't drop any frames while waiting for the HAL to send
|
||||
// that notification. We can't be completely sure this is safe from deadlocking, though, since
|
||||
|
@ -451,27 +595,30 @@
|
|||
// cause deadlocks.
|
||||
BGMLogAndSwallowExceptionsMsg("BGMAudioDeviceManager::startPlayThroughSync",
|
||||
"Starting playthrough", [&] {
|
||||
playThrough.Start();
|
||||
pt.Start();
|
||||
});
|
||||
|
||||
err = playThrough.WaitForOutputDeviceToStart();
|
||||
err = pt.WaitForOutputDeviceToStart();
|
||||
BGMAssert(err != BGMPlayThrough::kDeviceNotStarting, "Playthrough didn't start");
|
||||
} else {
|
||||
LogWarning("BGMAudioDeviceManager::startPlayThroughSync: Didn't get state lock. Returning "
|
||||
"early with kBGMErrorCode_ReturningEarly.");
|
||||
err = kBGMErrorCode_ReturningEarly;
|
||||
|
||||
// TODO: The QOS_CLASS_USER_INTERACTIVE constant isn't available on OS X 10.9.
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
|
||||
@try {
|
||||
[stateLock lock];
|
||||
|
||||
BGMPlayThrough& pt = (forUISoundsDevice ? playThrough_UISounds : playThrough);
|
||||
|
||||
BGMLogAndSwallowExceptionsMsg("BGMAudioDeviceManager::startPlayThroughSync",
|
||||
"Starting playthrough (dispatched)", [&] {
|
||||
playThrough.Start();
|
||||
pt.Start();
|
||||
});
|
||||
|
||||
BGMLogAndSwallowExceptions("BGMAudioDeviceManager::startPlayThroughSync", [&] {
|
||||
playThrough.StopIfIdle();
|
||||
pt.StopIfIdle();
|
||||
});
|
||||
} @finally {
|
||||
[stateLock unlock];
|
||||
|
|
|
@ -50,10 +50,10 @@ static UInt64 const kPauseDelayNSec = 1500 * NSEC_PER_MSEC;
|
|||
// immediately if we haven't been paused for long and the non-music-player client stops IO? That would usually indicate that
|
||||
// it doesn't intend to start playing audio again soon. We'd also have to deal with music players that don't stop IO when
|
||||
// they're paused.
|
||||
static UInt64 const kMaxUnpauseDelayNSec = 3000 * NSEC_PER_MSEC;
|
||||
static UInt64 const kMaxUnpauseDelayNSec = 3500 * NSEC_PER_MSEC;
|
||||
static UInt64 const kMinUnpauseDelayNSec = kMaxUnpauseDelayNSec / 10;
|
||||
// We multiply the time spent paused by this factor to calculate the delay before we consider unpausing.
|
||||
static Float32 const kUnpauseDelayWeightingFactor = 0.25f;
|
||||
static Float32 const kUnpauseDelayWeightingFactor = 0.1f;
|
||||
|
||||
@implementation BGMAutoPauseMusic {
|
||||
BOOL enabled;
|
||||
|
@ -118,7 +118,7 @@ static Float32 const kUnpauseDelayWeightingFactor = 0.25f;
|
|||
// so we have to check them all
|
||||
for (int i = 0; i < inNumberAddresses; i++) {
|
||||
if (inAddresses[i].mSelector == kAudioDeviceCustomPropertyDeviceAudibleState) {
|
||||
SInt32 audibleState = [weakSelf deviceAudibleState];
|
||||
BGMDeviceAudibleState audibleState = [weakSelf deviceAudibleState];
|
||||
|
||||
#if DEBUG
|
||||
const char audibleStateStr[5] = CA4CCToCString(audibleState);
|
||||
|
@ -144,8 +144,8 @@ static Float32 const kUnpauseDelayWeightingFactor = 0.25f;
|
|||
};
|
||||
}
|
||||
|
||||
- (SInt32) deviceAudibleState {
|
||||
SInt32 audibleState;
|
||||
- (BGMDeviceAudibleState) deviceAudibleState {
|
||||
BGMDeviceAudibleState audibleState;
|
||||
CFNumberRef audibleStateRef =
|
||||
static_cast<CFNumberRef>([audioDevices bgmDevice].GetPropertyData_CFType(kBGMAudibleStateAddress));
|
||||
|
||||
|
@ -188,8 +188,8 @@ static Float32 const kUnpauseDelayWeightingFactor = 0.25f;
|
|||
// Unpause sooner if we've only been paused for a short time. This is so a notification sound causing an auto-pause is
|
||||
// less of an interruption.
|
||||
//
|
||||
// TODO: Would it help much if we ignored all audio played on the "system default" device rather than the "default"
|
||||
// device? IIRC apps are supposed to use the former for UI sounds.
|
||||
// TODO: Fading in and out would make short pauses a lot less jarring because, if they were short enough, we wouldn't
|
||||
// actually pause the music player. So you'd hear a dip in the music's volume rather than a gap.
|
||||
UInt64 unpauseDelayNsec =
|
||||
static_cast<UInt64>((wentSilent - wentAudible) * kUnpauseDelayWeightingFactor);
|
||||
|
||||
|
|
|
@ -47,8 +47,7 @@ BGMDeviceControlsList::BGMDeviceControlsList(AudioObjectID inBGMDevice,
|
|||
mBGMDevice(inBGMDevice),
|
||||
mAudioSystem(inAudioSystem)
|
||||
{
|
||||
BGMAssert((mBGMDevice.GetObjectID() == mAudioSystem.GetAudioDeviceForUID(CFSTR(kBGMDeviceUID)) ||
|
||||
mBGMDevice.GetObjectID() == kAudioObjectUnknown),
|
||||
BGMAssert((mBGMDevice.IsBGMDevice() || mBGMDevice.GetObjectID() == kAudioObjectUnknown),
|
||||
"BGMDeviceControlsList::BGMDeviceControlsList: Given device is not BGMDevice");
|
||||
|
||||
#pragma clang diagnostic push
|
||||
|
@ -117,7 +116,7 @@ void BGMDeviceControlsList::SetBGMDevice(AudioObjectID inBGMDeviceID)
|
|||
|
||||
mBGMDevice = inBGMDeviceID;
|
||||
|
||||
BGMAssert(mBGMDevice.GetObjectID() == mAudioSystem.GetAudioDeviceForUID(CFSTR(kBGMDeviceUID)),
|
||||
BGMAssert(mBGMDevice.IsBGMDevice(),
|
||||
"BGMDeviceControlsList::SetBGMDevice: Given device is not BGMDevice");
|
||||
}
|
||||
|
||||
|
@ -127,7 +126,7 @@ bool BGMDeviceControlsList::MatchControlsListOf(AudioObjectID inDeviceID)
|
|||
{
|
||||
CAMutex::Locker locker(mMutex);
|
||||
|
||||
if(mBGMDevice.GetObjectID() != mAudioSystem.GetAudioDeviceForUID(CFSTR(kBGMDeviceUID)))
|
||||
if(!mBGMDevice.IsBGMDevice())
|
||||
{
|
||||
LogWarning("BGMDeviceControlsList::MatchControlsListOf: BGMDevice ID not set");
|
||||
return false;
|
||||
|
@ -291,7 +290,7 @@ void BGMDeviceControlsList::InitDeviceToggling()
|
|||
return;
|
||||
}
|
||||
|
||||
BGMAssert(mBGMDevice.GetObjectID() == mAudioSystem.GetAudioDeviceForUID(CFSTR(kBGMDeviceUID)),
|
||||
BGMAssert(mBGMDevice.IsBGMDevice(),
|
||||
"BGMDeviceControlsList::InitDeviceToggling: mBGMDevice device is not set to "
|
||||
"BGMDevice's ID");
|
||||
|
||||
|
@ -308,7 +307,7 @@ void BGMDeviceControlsList::InitDeviceToggling()
|
|||
#pragma clang diagnostic pop
|
||||
mListenerQueue = dispatch_queue_create("com.bearisdriving.BGM.BGMDeviceControlsList", attr);
|
||||
|
||||
mListenerBlock = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses) {
|
||||
auto listenerBlock = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses) {
|
||||
// Ignore the notification if we're not toggling the default device, which would just mean
|
||||
// the default device has been changed for an unrelated reason.
|
||||
if(mDeviceToggleState == ToggleState::NotToggling)
|
||||
|
@ -339,10 +338,15 @@ void BGMDeviceControlsList::InitDeviceToggling()
|
|||
mDeviceToggleBlock);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mListenerBlock = Block_copy(listenerBlock);
|
||||
|
||||
BGMLogAndSwallowExceptions("BGMDeviceControlsList::InitDeviceToggling", [&] {
|
||||
mAudioSystem.AddPropertyListenerBlock(CAPropertyAddress(kAudioHardwarePropertyDevices),
|
||||
mListenerQueue,
|
||||
|
@ -471,8 +475,7 @@ dispatch_block_t BGMDeviceControlsList::CreateDisableNullDeviceBlock()
|
|||
SetNullDeviceEnabled(false);
|
||||
}));
|
||||
|
||||
BGMAssert(mBGMDevice.GetObjectID() == mAudioSystem.GetAudioDeviceForUID(CFSTR(kBGMDeviceUID)),
|
||||
"BGMDevice's AudioObjectID changed");
|
||||
BGMAssert(mBGMDevice.IsBGMDevice(), "BGMDevice's AudioObjectID changed");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ static const UInt32 kStopIOProcTimeoutInIOCycles = 600;
|
|||
|
||||
#pragma mark Construction/Destruction
|
||||
|
||||
BGMPlayThrough::BGMPlayThrough(CAHALAudioDevice inInputDevice, CAHALAudioDevice inOutputDevice)
|
||||
BGMPlayThrough::BGMPlayThrough(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice)
|
||||
:
|
||||
mInputDevice(inInputDevice),
|
||||
mOutputDevice(inOutputDevice)
|
||||
|
@ -68,7 +68,7 @@ BGMPlayThrough::~BGMPlayThrough()
|
|||
}
|
||||
}
|
||||
|
||||
void BGMPlayThrough::Init(CAHALAudioDevice inInputDevice, CAHALAudioDevice inOutputDevice)
|
||||
void BGMPlayThrough::Init(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice)
|
||||
{
|
||||
BGMAssert(mInputDeviceIOProcState.is_lock_free(),
|
||||
"BGMPlayThrough::BGMPlayThrough: !mInputDeviceIOProcState.is_lock_free()");
|
||||
|
@ -151,7 +151,7 @@ void BGMPlayThrough::Activate()
|
|||
|
||||
bool isBGMDevice = true;
|
||||
CATry
|
||||
isBGMDevice = IsBGMDevice(mInputDevice);
|
||||
isBGMDevice = mInputDevice.IsBGMDeviceInstance();
|
||||
CACatch
|
||||
|
||||
if(isBGMDevice)
|
||||
|
@ -164,7 +164,7 @@ void BGMPlayThrough::Activate()
|
|||
{
|
||||
LogWarning("BGMPlayThrough::Activate: Playthrough activated with an output device other "
|
||||
"than BGMDevice. This hasn't been tested and is almost definitely a bug.");
|
||||
BGMAssert(false, "BGMPlayThrough::Activate: !IsBGMDevice(mInputDevice)");
|
||||
BGMAssert(false, "BGMPlayThrough::Activate: !mInputDevice.IsBGMDeviceInstance()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ void BGMPlayThrough::Deactivate()
|
|||
bool inputDeviceIsBGMDevice = true;
|
||||
|
||||
CATry
|
||||
inputDeviceIsBGMDevice = IsBGMDevice(mInputDevice);
|
||||
inputDeviceIsBGMDevice = mInputDevice.IsBGMDeviceInstance();
|
||||
CACatch
|
||||
|
||||
// Unregister notification listeners.
|
||||
|
@ -188,30 +188,30 @@ void BGMPlayThrough::Deactivate()
|
|||
{
|
||||
// There's not much we can do if these calls throw. The docs for AudioObjectRemovePropertyListener
|
||||
// just say that means it failed.
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&]() {
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&] {
|
||||
mInputDevice.RemovePropertyListener(CAPropertyAddress(kAudioDevicePropertyDeviceIsRunning),
|
||||
&BGMPlayThrough::BGMDeviceListenerProc,
|
||||
this);
|
||||
});
|
||||
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&]() {
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&] {
|
||||
mInputDevice.RemovePropertyListener(CAPropertyAddress(kAudioDeviceProcessorOverload),
|
||||
&BGMPlayThrough::BGMDeviceListenerProc,
|
||||
this);
|
||||
});
|
||||
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&]() {
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&] {
|
||||
mInputDevice.RemovePropertyListener(kBGMRunningSomewhereOtherThanBGMAppAddress,
|
||||
&BGMPlayThrough::BGMDeviceListenerProc,
|
||||
this);
|
||||
});
|
||||
}
|
||||
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&]() {
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&] {
|
||||
Stop();
|
||||
});
|
||||
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&]() {
|
||||
BGMLogAndSwallowExceptions("BGMPlayThrough::Deactivate", [&] {
|
||||
DestroyIOProcIDs();
|
||||
});
|
||||
|
||||
|
@ -240,15 +240,6 @@ void BGMPlayThrough::AllocateBuffer()
|
|||
mOutputDevice.GetIOBufferSize() * 20);
|
||||
}
|
||||
|
||||
// static
|
||||
bool BGMPlayThrough::IsBGMDevice(CAHALAudioDevice inDevice)
|
||||
{
|
||||
CFStringRef uid = inDevice.CopyDeviceUID();
|
||||
bool isBGMDevice = CFEqual(uid, CFSTR(kBGMDeviceUID));
|
||||
CFRelease(uid);
|
||||
return isBGMDevice;
|
||||
}
|
||||
|
||||
void BGMPlayThrough::CreateIOProcIDs()
|
||||
{
|
||||
CAMutex::Locker stateLocker(mStateMutex);
|
||||
|
@ -326,7 +317,7 @@ void BGMPlayThrough::DestroyIOProcIDs()
|
|||
|
||||
DebugMsg("BGMPlayThrough::DestroyIOProcIDs: Destroying IOProcs");
|
||||
|
||||
auto destroy = [](CAHALAudioDevice& device, const char* deviceName, AudioDeviceIOProcID& ioProcID) {
|
||||
auto destroy = [](BGMAudioDevice& device, const char* deviceName, AudioDeviceIOProcID& ioProcID) {
|
||||
#if !DEBUG
|
||||
#pragma unused (deviceName)
|
||||
#endif
|
||||
|
@ -382,8 +373,8 @@ bool BGMPlayThrough::CheckIOProcsAreStopped() const noexcept
|
|||
return statesOK;
|
||||
}
|
||||
|
||||
void BGMPlayThrough::SetDevices(CAHALAudioDevice* __nullable inInputDevice,
|
||||
CAHALAudioDevice* __nullable inOutputDevice)
|
||||
void BGMPlayThrough::SetDevices(BGMAudioDevice* __nullable inInputDevice,
|
||||
BGMAudioDevice* __nullable inOutputDevice)
|
||||
{
|
||||
CAMutex::Locker stateLocker(mStateMutex);
|
||||
|
||||
|
@ -734,7 +725,7 @@ void BGMPlayThrough::StopIfIdle()
|
|||
|
||||
CAMutex::Locker stateLocker(mStateMutex);
|
||||
|
||||
BGMAssert(IsBGMDevice(mInputDevice),
|
||||
BGMAssert(mInputDevice.IsBGMDeviceInstance(),
|
||||
"BGMDevice not set as input device. StopIfIdle can't tell if other devices are idle.");
|
||||
|
||||
if(!IsRunningSomewhereOtherThanBGMApp(mInputDevice))
|
||||
|
@ -904,7 +895,7 @@ void BGMPlayThrough::HandleBGMDeviceIsRunningSomewhereOtherThanBGMApp(BGMPlay
|
|||
}
|
||||
|
||||
// static
|
||||
bool BGMPlayThrough::IsRunningSomewhereOtherThanBGMApp(const CAHALAudioDevice& inBGMDevice)
|
||||
bool BGMPlayThrough::IsRunningSomewhereOtherThanBGMApp(const BGMAudioDevice& inBGMDevice)
|
||||
{
|
||||
return CFBooleanGetValue(
|
||||
static_cast<CFBooleanRef>(
|
||||
|
@ -1078,7 +1069,7 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
|
|||
bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
|
||||
std::atomic<IOState>& inState,
|
||||
AudioDeviceIOProcID __nullable inIOProcID,
|
||||
CAHALAudioDevice& inDevice,
|
||||
BGMAudioDevice& inDevice,
|
||||
IOState& outNewState)
|
||||
{
|
||||
BGMAssert(inIOProcID != nullptr, "BGMPlayThrough::UpdateIOProcState: !inIOProcID");
|
||||
|
|
|
@ -39,9 +39,11 @@
|
|||
#ifndef BGMApp__BGMPlayThrough
|
||||
#define BGMApp__BGMPlayThrough
|
||||
|
||||
// Local Includes
|
||||
#include "BGMAudioDevice.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CARingBuffer.h"
|
||||
#include "CAHALAudioDevice.h"
|
||||
#include "CAMutex.h"
|
||||
|
||||
// STL Includes
|
||||
|
@ -62,7 +64,7 @@ public:
|
|||
static const OSStatus kDeviceNotStarting = 100;
|
||||
|
||||
public:
|
||||
BGMPlayThrough(CAHALAudioDevice inInputDevice, CAHALAudioDevice inOutputDevice);
|
||||
BGMPlayThrough(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice);
|
||||
~BGMPlayThrough();
|
||||
// Disallow copying
|
||||
BGMPlayThrough(const BGMPlayThrough&) = delete;
|
||||
|
@ -76,7 +78,7 @@ public:
|
|||
|
||||
private:
|
||||
/*! @throws CAException */
|
||||
void Init(CAHALAudioDevice inInputDevice, CAHALAudioDevice inOutputDevice);
|
||||
void Init(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice);
|
||||
|
||||
public:
|
||||
/*! @throws CAException */
|
||||
|
@ -86,8 +88,6 @@ public:
|
|||
|
||||
private:
|
||||
void AllocateBuffer();
|
||||
|
||||
static bool IsBGMDevice(CAHALAudioDevice inDevice);
|
||||
|
||||
/*! @throws CAException */
|
||||
void CreateIOProcIDs();
|
||||
|
@ -104,8 +104,8 @@ public:
|
|||
Pass null for either param to only change one of the devices.
|
||||
@throws CAException
|
||||
*/
|
||||
void SetDevices(CAHALAudioDevice* __nullable inInputDevice,
|
||||
CAHALAudioDevice* __nullable inOutputDevice);
|
||||
void SetDevices(BGMAudioDevice* __nullable inInputDevice,
|
||||
BGMAudioDevice* __nullable inOutputDevice);
|
||||
|
||||
/*! @throws CAException */
|
||||
void Start();
|
||||
|
@ -130,7 +130,7 @@ private:
|
|||
static void HandleBGMDeviceIsRunning(BGMPlayThrough* refCon);
|
||||
static void HandleBGMDeviceIsRunningSomewhereOtherThanBGMApp(BGMPlayThrough* refCon);
|
||||
|
||||
static bool IsRunningSomewhereOtherThanBGMApp(const CAHALAudioDevice& inBGMDevice);
|
||||
static bool IsRunningSomewhereOtherThanBGMApp(const BGMAudioDevice& inBGMDevice);
|
||||
|
||||
static OSStatus InputDeviceIOProc(AudioObjectID inDevice,
|
||||
const AudioTimeStamp* inNow,
|
||||
|
@ -159,7 +159,7 @@ private:
|
|||
static bool UpdateIOProcState(const char* __nullable callerName,
|
||||
std::atomic<IOState>& inState,
|
||||
AudioDeviceIOProcID __nullable inIOProcID,
|
||||
CAHALAudioDevice& inDevice,
|
||||
BGMAudioDevice& inDevice,
|
||||
IOState& outNewState);
|
||||
|
||||
static void HandleRingBufferError(CARingBufferError err,
|
||||
|
@ -172,8 +172,8 @@ private:
|
|||
AudioDeviceIOProcID __nullable mInputDeviceIOProcID;
|
||||
AudioDeviceIOProcID __nullable mOutputDeviceIOProcID;
|
||||
|
||||
CAHALAudioDevice mInputDevice { kAudioObjectUnknown };
|
||||
CAHALAudioDevice mOutputDevice { kAudioObjectUnknown };
|
||||
BGMAudioDevice mInputDevice { kAudioObjectUnknown };
|
||||
BGMAudioDevice mOutputDevice { kAudioObjectUnknown };
|
||||
|
||||
CAMutex mStateMutex { "Playthrough state" };
|
||||
|
||||
|
|
|
@ -177,12 +177,12 @@
|
|||
return YES;
|
||||
}
|
||||
|
||||
- (void) startPlayThroughSyncWithReply:(void (^)(NSError*))reply {
|
||||
- (void) startPlayThroughSyncWithReply:(void (^)(NSError*))reply forUISoundsDevice:(BOOL)isUI {
|
||||
NSString* description;
|
||||
OSStatus err;
|
||||
|
||||
try {
|
||||
err = [audioDevices startPlayThroughSync];
|
||||
err = [audioDevices startPlayThroughSync:isUI];
|
||||
} catch (CAException e) {
|
||||
// startPlayThroughSync should never throw a CAException, but check anyway in case we change that at some point.
|
||||
LogError("BGMXPCListener::startPlayThroughSyncWithReply: Caught CAException (%d). Replying kBGMXPC_HardwareError.",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<development version="7000" identifier="xcode"/>
|
||||
|
@ -210,7 +210,7 @@
|
|||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" id="Cdb-RA-YK0">
|
||||
<rect key="frame" x="1" y="1" width="565" height="243"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView ambiguous="YES" editable="NO" importsGraphics="NO" richText="NO" id="LSG-PF-cl8">
|
||||
<rect key="frame" x="-6" y="0.0" width="576.5" height="243"/>
|
||||
|
@ -231,7 +231,7 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="qCC-lY-zQ6">
|
||||
<rect key="frame" x="550" y="1" width="16" height="243"/>
|
||||
<rect key="frame" x="-15" y="1" width="16" height="0.0"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
|
|
|
@ -187,7 +187,7 @@ static NSInteger const kOutputDeviceMenuItemTag = 2;
|
|||
item.toolTip = toolTip;
|
||||
item.target = self;
|
||||
item.indentationLevel = 1;
|
||||
item.representedObject = @{ @"deviceID": [NSNumber numberWithUnsignedInt:device.GetObjectID()],
|
||||
item.representedObject = @{ @"deviceID": @(device.GetObjectID()),
|
||||
@"dataSourceID": dataSourceID ? BGMNN(dataSourceID) : [NSNull null] };
|
||||
|
||||
return item;
|
||||
|
|
|
@ -155,7 +155,7 @@ static NSXPCConnection* __nullable sBGMAppConnection = nil;
|
|||
}
|
||||
}
|
||||
|
||||
- (void) startBGMAppPlayThroughSyncWithReply:(void (^)(NSError*))reply {
|
||||
- (void) startBGMAppPlayThroughSyncWithReply:(void (^)(NSError*))reply forUISoundsDevice:(BOOL)isUI {
|
||||
[self debugWarnIfCalledByBGMApp];
|
||||
|
||||
// If this reply string isn't set before the end of this method, it's a bug
|
||||
|
@ -173,7 +173,7 @@ static NSXPCConnection* __nullable sBGMAppConnection = nil;
|
|||
[remoteObjectProxy startPlayThroughSyncWithReply:^(NSError* bgmAppReply) {
|
||||
replyToBGMDriver = bgmAppReply;
|
||||
dispatch_semaphore_signal(bgmAppReplySemaphore);
|
||||
}];
|
||||
} forUISoundsDevice:isUI];
|
||||
} errorHandler:^(NSError* error) {
|
||||
replyToBGMDriver = [BGMXPCHelperService errorWithCode:kBGMXPC_MessageFailure
|
||||
description:[error localizedDescription]
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
@"Check that BGMApp isn't running, which would cause this failure");
|
||||
|
||||
dispatch_semaphore_signal(replySemaphore);
|
||||
}];
|
||||
} forUISoundsDevice:NO];
|
||||
|
||||
if (0 != dispatch_semaphore_wait(replySemaphore, dispatch_time(DISPATCH_TIME_NOW, kStartIOTimeoutNsec))) {
|
||||
XCTFail(@"Timed out waiting for BGMXPCHelper");
|
||||
|
|
|
@ -7,12 +7,20 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
19FE742AEBE30B21C4CF9285 /* BGM_Control.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7BC3396C4E50D21E1BC8 /* BGM_Control.cpp */; };
|
||||
19FE761291BF07AEA278F25C /* BGM_MuteControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7E6DC2A1B61211D74782 /* BGM_MuteControl.cpp */; };
|
||||
19FE766482B57D852CCF6F0A /* BGM_MuteControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7E6DC2A1B61211D74782 /* BGM_MuteControl.cpp */; };
|
||||
19FE77D40F15EA060B462D83 /* BGM_Control.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7BC3396C4E50D21E1BC8 /* BGM_Control.cpp */; };
|
||||
1C0CB6B91C642C600084C15A /* BGM_Client.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0CB6B01C642C600084C15A /* BGM_Client.cpp */; };
|
||||
1C0CB6BA1C642C600084C15A /* BGM_ClientMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0CB6B21C642C600084C15A /* BGM_ClientMap.cpp */; };
|
||||
1C0CB6BB1C642C600084C15A /* BGM_Clients.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C0CB6B41C642C600084C15A /* BGM_Clients.cpp */; };
|
||||
1C30A69F1C1E98F000C05AA5 /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B3841BBCEFE8000E2DD1 /* CAMutex.cpp */; };
|
||||
1C38210E1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C38210C1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp */; };
|
||||
1C3DB4871BE063C500EC8160 /* BGM_DeviceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C3DB4861BE063C500EC8160 /* BGM_DeviceTests.mm */; };
|
||||
1C7010751F05ED5100D8CCDC /* BGM_AudibleState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010731F05ED5100D8CCDC /* BGM_AudibleState.cpp */; };
|
||||
1C7010761F05ED5100D8CCDC /* BGM_AudibleState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010731F05ED5100D8CCDC /* BGM_AudibleState.cpp */; };
|
||||
1C7010791F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */; };
|
||||
1C70107A1F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */; };
|
||||
1C8034DD1BDD073B00668E00 /* BGM_ClientsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C8034DC1BDD073B00668E00 /* BGM_ClientsTests.mm */; };
|
||||
1CA2A9E21E8D1D08007A76A4 /* BGM_Stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CA2A9E01E8D1D08007A76A4 /* BGM_Stream.cpp */; };
|
||||
1CB8B36E1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B36D1BBBD541000E2DD1 /* BGM_PlugInInterface.cpp */; };
|
||||
|
@ -71,6 +79,10 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
19FE7431C588F36F4F1E70BB /* BGM_MuteControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_MuteControl.h; sourceTree = "<group>"; };
|
||||
19FE7B8CE9148B3D8D7517C6 /* BGM_Control.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_Control.h; sourceTree = "<group>"; };
|
||||
19FE7BC3396C4E50D21E1BC8 /* BGM_Control.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_Control.cpp; sourceTree = "<group>"; };
|
||||
19FE7E6DC2A1B61211D74782 /* BGM_MuteControl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_MuteControl.cpp; sourceTree = "<group>"; };
|
||||
1C0CB6A61C4E06C00084C15A /* CAAtomicStack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAAtomicStack.h; path = PublicUtility/CAAtomicStack.h; sourceTree = "<group>"; };
|
||||
1C0CB6A71C4E06F70084C15A /* CAAtomic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAAtomic.h; path = PublicUtility/CAAtomic.h; sourceTree = "<group>"; };
|
||||
1C0CB6A91C50A3AF0084C15A /* CAAutoDisposer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAAutoDisposer.h; path = PublicUtility/CAAutoDisposer.h; sourceTree = "<group>"; };
|
||||
|
@ -89,6 +101,10 @@
|
|||
1C38210F1C4A18DE00A0C8C6 /* CAPThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAPThread.cpp; path = PublicUtility/CAPThread.cpp; sourceTree = "<group>"; };
|
||||
1C3821101C4A18DE00A0C8C6 /* CAPThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPThread.h; path = PublicUtility/CAPThread.h; sourceTree = "<group>"; };
|
||||
1C3DB4861BE063C500EC8160 /* BGM_DeviceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGM_DeviceTests.mm; sourceTree = "<group>"; };
|
||||
1C7010731F05ED5100D8CCDC /* BGM_AudibleState.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_AudibleState.cpp; sourceTree = "<group>"; };
|
||||
1C7010741F05ED5100D8CCDC /* BGM_AudibleState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_AudibleState.h; sourceTree = "<group>"; };
|
||||
1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGM_VolumeControl.cpp; sourceTree = "<group>"; };
|
||||
1C7010781F07A0BA00D8CCDC /* BGM_VolumeControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGM_VolumeControl.h; sourceTree = "<group>"; };
|
||||
1C8034DA1BDD073B00668E00 /* BGMDriverTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BGMDriverTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1C8034DC1BDD073B00668E00 /* BGM_ClientsTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGM_ClientsTests.mm; sourceTree = "<group>"; };
|
||||
1C8034DE1BDD073B00668E00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -235,10 +251,18 @@
|
|||
1CDF3ABD1E8644C20001E9B7 /* BGM_AbstractDevice.cpp */,
|
||||
1CB8B37F1BBCCF87000E2DD1 /* BGM_Device.h */,
|
||||
1CB8B37E1BBCCF87000E2DD1 /* BGM_Device.cpp */,
|
||||
1C7010741F05ED5100D8CCDC /* BGM_AudibleState.h */,
|
||||
1C7010731F05ED5100D8CCDC /* BGM_AudibleState.cpp */,
|
||||
1CDF3ABB1E863B980001E9B7 /* BGM_NullDevice.h */,
|
||||
1CDF3ABA1E863B980001E9B7 /* BGM_NullDevice.cpp */,
|
||||
1CA2A9E11E8D1D08007A76A4 /* BGM_Stream.h */,
|
||||
1CA2A9E01E8D1D08007A76A4 /* BGM_Stream.cpp */,
|
||||
19FE7B8CE9148B3D8D7517C6 /* BGM_Control.h */,
|
||||
19FE7BC3396C4E50D21E1BC8 /* BGM_Control.cpp */,
|
||||
1C7010781F07A0BA00D8CCDC /* BGM_VolumeControl.h */,
|
||||
1C7010771F07A0BA00D8CCDC /* BGM_VolumeControl.cpp */,
|
||||
19FE7431C588F36F4F1E70BB /* BGM_MuteControl.h */,
|
||||
19FE7E6DC2A1B61211D74782 /* BGM_MuteControl.cpp */,
|
||||
1C0CB6AF1C642C600084C15A /* DeviceClients */,
|
||||
1C38210D1C4A163A00A0C8C6 /* BGM_TaskQueue.h */,
|
||||
1C38210C1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp */,
|
||||
|
@ -453,6 +477,7 @@
|
|||
27E6B5F01E01966A00EC0AAB /* BGM_Utils.cpp in Sources */,
|
||||
277170101CA0CFC300AB34B4 /* BGM_PlugInInterface.cpp in Sources */,
|
||||
277170111CA0CFC300AB34B4 /* CACFNumber.cpp in Sources */,
|
||||
1C7010761F05ED5100D8CCDC /* BGM_AudibleState.cpp in Sources */,
|
||||
27D643C31C9FBE1600737F6E /* BGM_XPCHelper.m in Sources */,
|
||||
27379B821C76D62D0084A24C /* CADebugMacros.cpp in Sources */,
|
||||
27379B831C76D62D0084A24C /* CADebugPrintf.cpp in Sources */,
|
||||
|
@ -465,6 +490,7 @@
|
|||
277EE65E1C728C9D0037F1EE /* BGM_PlugIn.cpp in Sources */,
|
||||
277EE65F1C728C9D0037F1EE /* CAPThread.cpp in Sources */,
|
||||
277EE65A1C728C630037F1EE /* BGM_Client.cpp in Sources */,
|
||||
1C70107A1F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */,
|
||||
277EE65B1C728C630037F1EE /* BGM_ClientMap.cpp in Sources */,
|
||||
277EE65C1C728C630037F1EE /* BGM_Clients.cpp in Sources */,
|
||||
277EE65D1C728C630037F1EE /* BGM_TaskQueue.cpp in Sources */,
|
||||
|
@ -476,6 +502,8 @@
|
|||
1CC1DF8D1BE5705700FB8FE4 /* CACFDictionary.cpp in Sources */,
|
||||
1C3DB4871BE063C500EC8160 /* BGM_DeviceTests.mm in Sources */,
|
||||
1C8034DD1BDD073B00668E00 /* BGM_ClientsTests.mm in Sources */,
|
||||
19FE761291BF07AEA278F25C /* BGM_MuteControl.cpp in Sources */,
|
||||
19FE742AEBE30B21C4CF9285 /* BGM_Control.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -484,6 +512,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1CA2A9E21E8D1D08007A76A4 /* BGM_Stream.cpp in Sources */,
|
||||
1C7010751F05ED5100D8CCDC /* BGM_AudibleState.cpp in Sources */,
|
||||
1CB8B3801BBCCF87000E2DD1 /* BGM_Device.cpp in Sources */,
|
||||
1C0CB6B91C642C600084C15A /* BGM_Client.cpp in Sources */,
|
||||
1CB8B3921BBCF50A000E2DD1 /* BGM_WrappedAudioEngine.cpp in Sources */,
|
||||
|
@ -491,12 +520,15 @@
|
|||
1CB8B37D1BBCCF62000E2DD1 /* BGM_PlugIn.cpp in Sources */,
|
||||
27381A161C8EF50F00DF167C /* BGM_XPCHelper.m in Sources */,
|
||||
1CDF3ABF1E8644C20001E9B7 /* BGM_AbstractDevice.cpp in Sources */,
|
||||
1C7010791F07A0BA00D8CCDC /* BGM_VolumeControl.cpp in Sources */,
|
||||
1C0CB6BA1C642C600084C15A /* BGM_ClientMap.cpp in Sources */,
|
||||
1CB8B3831BBCE7B5000E2DD1 /* BGM_Object.cpp in Sources */,
|
||||
275343BD1DE9B44900DF3858 /* BGM_Utils.cpp in Sources */,
|
||||
1C38210E1C4A163A00A0C8C6 /* BGM_TaskQueue.cpp in Sources */,
|
||||
1C0CB6BB1C642C600084C15A /* BGM_Clients.cpp in Sources */,
|
||||
1CDF3ABC1E863B980001E9B7 /* BGM_NullDevice.cpp in Sources */,
|
||||
19FE766482B57D852CCF6F0A /* BGM_MuteControl.cpp in Sources */,
|
||||
19FE77D40F15EA060B462D83 /* BGM_Control.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -760,6 +792,7 @@
|
|||
2743C9C81D7EF84B0089613B /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
|
@ -788,6 +821,7 @@
|
|||
2743C9C91D7EF84B0089613B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
|
|
212
BGMDriver/BGMDriver/BGM_AudibleState.cpp
Normal file
212
BGMDriver/BGMDriver/BGM_AudibleState.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGM_AudibleState.cpp
|
||||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
// Copyright © 2016 Josh Junon
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "BGM_AudibleState.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CADebugMacros.h"
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion"
|
||||
#include "CAAtomic.h"
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// STL Includes
|
||||
#include <algorithm> // For std::min and std::max.
|
||||
|
||||
|
||||
// TODO: This is just the first value I tried.
|
||||
static const Float32 kSampleVolumeMarginRaw = 0.0001f;
|
||||
|
||||
BGM_AudibleState::BGM_AudibleState()
|
||||
:
|
||||
mState(kBGMDeviceIsSilent),
|
||||
mSampleTimes({0, 0, 0, 0})
|
||||
{
|
||||
}
|
||||
|
||||
BGMDeviceAudibleState BGM_AudibleState::GetState() const noexcept
|
||||
{
|
||||
CAMemoryBarrier(); // Probably unnecessary.
|
||||
return mState;
|
||||
}
|
||||
|
||||
void BGM_AudibleState::Reset() noexcept
|
||||
{
|
||||
mState = kBGMDeviceIsSilent;
|
||||
|
||||
mSampleTimes.latestSilent = 0;
|
||||
mSampleTimes.latestAudibleNonMusic = 0;
|
||||
mSampleTimes.latestSilentMusic = 0;
|
||||
mSampleTimes.latestAudibleMusic = 0;
|
||||
}
|
||||
|
||||
void BGM_AudibleState::UpdateWithClientIO(bool inClientIsMusicPlayer,
|
||||
UInt32 inIOBufferFrameSize,
|
||||
Float64 inOutputSampleTime,
|
||||
const Float32* inBuffer)
|
||||
{
|
||||
// Update the sample times of the most recent audible music, silent music and audible non-music
|
||||
// samples we've received.
|
||||
|
||||
Float64 endFrameSampleTime = inOutputSampleTime + inIOBufferFrameSize - 1;
|
||||
|
||||
if(inClientIsMusicPlayer)
|
||||
{
|
||||
if(BufferIsAudible(inIOBufferFrameSize, inBuffer))
|
||||
{
|
||||
mSampleTimes.latestAudibleMusic = std::max(mSampleTimes.latestAudibleMusic,
|
||||
endFrameSampleTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
mSampleTimes.latestSilentMusic = std::max(mSampleTimes.latestSilentMusic,
|
||||
endFrameSampleTime);
|
||||
}
|
||||
}
|
||||
else if(endFrameSampleTime > mSampleTimes.latestAudibleNonMusic && // Don't bother checking the
|
||||
// buffer if it won't change
|
||||
// anything.
|
||||
BufferIsAudible(inIOBufferFrameSize, inBuffer))
|
||||
{
|
||||
mSampleTimes.latestAudibleNonMusic = std::max(mSampleTimes.latestAudibleNonMusic,
|
||||
endFrameSampleTime);
|
||||
}
|
||||
}
|
||||
|
||||
bool BGM_AudibleState::UpdateWithMixedIO(UInt32 inIOBufferFrameSize,
|
||||
Float64 inOutputSampleTime,
|
||||
const Float32* inBuffer)
|
||||
{
|
||||
// Update the sample time of the most recent silent sample we've received. (The music player
|
||||
// client is not considered separate for the latest silent sample.)
|
||||
|
||||
bool audible = BufferIsAudible(inIOBufferFrameSize, inBuffer);
|
||||
|
||||
// The sample time of the last frame we're looking at.
|
||||
Float64 endFrameSampleTime = inOutputSampleTime + inIOBufferFrameSize - 1;
|
||||
|
||||
if(!audible)
|
||||
{
|
||||
mSampleTimes.latestSilent = std::max(mSampleTimes.latestSilent, endFrameSampleTime);
|
||||
}
|
||||
|
||||
return RecalculateState(endFrameSampleTime);
|
||||
}
|
||||
|
||||
bool BGM_AudibleState::RecalculateState(Float64 inEndFrameSampleTime)
|
||||
{
|
||||
Float64 sinceLatestSilent = inEndFrameSampleTime - mSampleTimes.latestSilent;
|
||||
Float64 sinceLatestMusicSilent = inEndFrameSampleTime - mSampleTimes.latestSilentMusic;
|
||||
Float64 sinceLatestAudible = inEndFrameSampleTime - mSampleTimes.latestAudibleNonMusic;
|
||||
Float64 sinceLatestMusicAudible = inEndFrameSampleTime - mSampleTimes.latestAudibleMusic;
|
||||
|
||||
bool didChangeState = false;
|
||||
|
||||
// Update mState
|
||||
|
||||
// Change from silent/silentExceptMusic to audible
|
||||
if(mState != kBGMDeviceIsAudible &&
|
||||
sinceLatestSilent >= kDeviceAudibleStateMinChangedFramesForUpdate &&
|
||||
// Check that non-music audio is currently playing
|
||||
sinceLatestAudible <= 0 && mSampleTimes.latestAudibleNonMusic != 0)
|
||||
{
|
||||
DebugMsg("BGM_AudibleState::RecalculateState: Changing "
|
||||
"kAudioDeviceCustomPropertyDeviceAudibleState to audible");
|
||||
mState = kBGMDeviceIsAudible;
|
||||
CAMemoryBarrier();
|
||||
didChangeState = true;
|
||||
}
|
||||
// Change from silent to silentExceptMusic
|
||||
else if(((mState == kBGMDeviceIsSilent &&
|
||||
sinceLatestMusicSilent >= kDeviceAudibleStateMinChangedFramesForUpdate) ||
|
||||
// ...or from audible to silentExceptMusic
|
||||
(mState == kBGMDeviceIsAudible &&
|
||||
sinceLatestAudible >= kDeviceAudibleStateMinChangedFramesForUpdate &&
|
||||
sinceLatestMusicSilent >= kDeviceAudibleStateMinChangedFramesForUpdate)) &&
|
||||
// In case we haven't seen any music samples yet (either audible or silent), check that
|
||||
// music is currently playing
|
||||
sinceLatestMusicAudible <= 0 && mSampleTimes.latestAudibleMusic != 0)
|
||||
{
|
||||
DebugMsg("BGM_AudibleState::RecalculateState: Changing "
|
||||
"kAudioDeviceCustomPropertyDeviceAudibleState to silent except music");
|
||||
mState = kBGMDeviceIsSilentExceptMusic;
|
||||
CAMemoryBarrier();
|
||||
didChangeState = true;
|
||||
}
|
||||
// Change from audible/silentExceptMusic to silent
|
||||
else if(mState != kBGMDeviceIsSilent &&
|
||||
sinceLatestAudible >= kDeviceAudibleStateMinChangedFramesForUpdate &&
|
||||
sinceLatestMusicAudible >= kDeviceAudibleStateMinChangedFramesForUpdate)
|
||||
{
|
||||
DebugMsg("BGM_AudibleState::RecalculateState: Changing "
|
||||
"kAudioDeviceCustomPropertyDeviceAudibleState to silent");
|
||||
mState = kBGMDeviceIsSilent;
|
||||
CAMemoryBarrier();
|
||||
didChangeState = true;
|
||||
}
|
||||
|
||||
return didChangeState;
|
||||
}
|
||||
|
||||
// static
|
||||
bool BGM_AudibleState::BufferIsAudible(UInt32 inIOBufferFrameSize, const Float32* inBuffer)
|
||||
{
|
||||
// Check each frame to see if any are audible. This could be much more accurate, but seems to
|
||||
// work well enough for now.
|
||||
//
|
||||
// The trade off here is between pausing the music player at the wrong time and unpausing it at
|
||||
// the wrong time. If a short sound (e.g. a UI alert) plays but has a long, barely-audible tail,
|
||||
// we might not detect the silence quickly enough and pause the music player. Similarly, if
|
||||
// we've paused the music player and there's a period of near-silence in the new audio, we might
|
||||
// unpause the music and briefly interrupt the new audio.
|
||||
//
|
||||
// A fairly long period of silence before unpausing the music player isn't a big problem, which
|
||||
// means BGMApp can wait much longer before unpausing than before pausing. So this function errs
|
||||
// toward considering the buffer silent, which helps BGMApp ignore short sounds.
|
||||
if(inIOBufferFrameSize > 0)
|
||||
{
|
||||
// Bounds for the left channel samples.
|
||||
Float32 firstSampleLLower = inBuffer[0] - kSampleVolumeMarginRaw;
|
||||
Float32 firstSampleLUpper = inBuffer[0] + kSampleVolumeMarginRaw;
|
||||
// Bounds for the right channel samples.
|
||||
Float32 firstSampleRLower = inBuffer[1] - kSampleVolumeMarginRaw;
|
||||
Float32 firstSampleRUpper = inBuffer[1] + kSampleVolumeMarginRaw;
|
||||
|
||||
for(UInt32 i = 0; i < inIOBufferFrameSize * 2; i += 2)
|
||||
{
|
||||
bool audibleL =
|
||||
(inBuffer[i] < firstSampleLLower) || (inBuffer[i] > firstSampleLUpper);
|
||||
bool audibleR =
|
||||
(inBuffer[i + 1] < firstSampleRLower) || (inBuffer[i + 1] > firstSampleRUpper);
|
||||
|
||||
if(audibleL || audibleR)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
103
BGMDriver/BGMDriver/BGM_AudibleState.h
Normal file
103
BGMDriver/BGMDriver/BGM_AudibleState.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGM_AudibleState.h
|
||||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
//
|
||||
// Inspects a stream of audio data and reports whether it's silent, silent except for the user's
|
||||
// music player, or audible.
|
||||
//
|
||||
// See kAudioDeviceCustomPropertyDeviceAudibleState and the BGMDeviceAudibleState enum in
|
||||
// BGM_Types.h for more info.
|
||||
//
|
||||
// Not thread-safe.
|
||||
//
|
||||
|
||||
#ifndef BGMDriver__BGM_AudibleState
|
||||
#define BGMDriver__BGM_AudibleState
|
||||
|
||||
// Local Includes
|
||||
#include "BGM_Types.h"
|
||||
|
||||
// System Includes
|
||||
#include <MacTypes.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
class BGM_AudibleState
|
||||
{
|
||||
|
||||
public:
|
||||
BGM_AudibleState();
|
||||
|
||||
/*!
|
||||
@return The current audible state of the device, to be used as the value of the
|
||||
kAudioDeviceCustomPropertyDeviceAudibleState property.
|
||||
*/
|
||||
BGMDeviceAudibleState GetState() const noexcept;
|
||||
|
||||
/*! Set the audible state back to kBGMDeviceIsSilent and ignore all previous IO. */
|
||||
void Reset() noexcept;
|
||||
|
||||
/*!
|
||||
Read an audio buffer sent by a single device client (i.e. a process playing audio) and update
|
||||
the audible state. The update will only affect the return value of GetState after the next
|
||||
call to UpdateWithMixedIO, when all IO for the cycle has been read.
|
||||
|
||||
Real-time safe. Not thread safe.
|
||||
*/
|
||||
void UpdateWithClientIO(bool inClientIsMusicPlayer,
|
||||
UInt32 inIOBufferFrameSize,
|
||||
Float64 inOutputSampleTime,
|
||||
const Float32* inBuffer);
|
||||
/*!
|
||||
Read a fully mixed audio buffer and update the audible state. All client (unmixed) buffers for
|
||||
the same cycle must be read with UpdateWithClientIO before calling this function.
|
||||
|
||||
Real-time safe. Not thread safe.
|
||||
|
||||
@return True if the audible state changed.
|
||||
*/
|
||||
bool UpdateWithMixedIO(UInt32 inIOBufferFrameSize,
|
||||
Float64 inOutputSampleTime,
|
||||
const Float32* inBuffer);
|
||||
|
||||
private:
|
||||
bool RecalculateState(Float64 inEndFrameSampleTime);
|
||||
|
||||
static bool BufferIsAudible(UInt32 inIOBufferFrameSize,
|
||||
const Float32* inBuffer);
|
||||
|
||||
private:
|
||||
BGMDeviceAudibleState mState;
|
||||
|
||||
struct
|
||||
{
|
||||
Float64 latestAudibleNonMusic;
|
||||
Float64 latestSilent;
|
||||
Float64 latestAudibleMusic;
|
||||
Float64 latestSilentMusic;
|
||||
} mSampleTimes;
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#endif /* BGMDriver__BGM_AudibleState */
|
||||
|
182
BGMDriver/BGMDriver/BGM_Control.cpp
Normal file
182
BGMDriver/BGMDriver/BGM_Control.cpp
Normal file
|
@ -0,0 +1,182 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGM_Control.cpp
|
||||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "BGM_Control.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CADebugMacros.h"
|
||||
#include "CAException.h"
|
||||
|
||||
// System Includes
|
||||
#include <CoreAudio/AudioHardwareBase.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
BGM_Control::BGM_Control(AudioObjectID inObjectID,
|
||||
AudioClassID inClassID,
|
||||
AudioClassID inBaseClassID,
|
||||
AudioObjectID inOwnerObjectID,
|
||||
AudioObjectPropertyScope inScope,
|
||||
AudioObjectPropertyElement inElement)
|
||||
:
|
||||
BGM_Object(inObjectID, inClassID, inBaseClassID, inOwnerObjectID),
|
||||
mScope(inScope),
|
||||
mElement(inElement)
|
||||
{
|
||||
}
|
||||
|
||||
bool BGM_Control::HasProperty(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
bool theAnswer = false;
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioControlPropertyScope:
|
||||
case kAudioControlPropertyElement:
|
||||
theAnswer = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
theAnswer = BGM_Object::HasProperty(inObjectID, inClientPID, inAddress);
|
||||
break;
|
||||
};
|
||||
|
||||
return theAnswer;
|
||||
}
|
||||
|
||||
bool BGM_Control::IsPropertySettable(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
bool theAnswer = false;
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioControlPropertyScope:
|
||||
case kAudioControlPropertyElement:
|
||||
theAnswer = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
theAnswer = BGM_Object::IsPropertySettable(inObjectID, inClientPID, inAddress);
|
||||
break;
|
||||
};
|
||||
|
||||
return theAnswer;
|
||||
}
|
||||
|
||||
UInt32 BGM_Control::GetPropertyDataSize(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
UInt32 theAnswer = 0;
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioControlPropertyScope:
|
||||
theAnswer = sizeof(AudioObjectPropertyScope);
|
||||
break;
|
||||
|
||||
case kAudioControlPropertyElement:
|
||||
theAnswer = sizeof(AudioObjectPropertyElement);
|
||||
break;
|
||||
|
||||
default:
|
||||
theAnswer = BGM_Object::GetPropertyDataSize(inObjectID,
|
||||
inClientPID,
|
||||
inAddress,
|
||||
inQualifierDataSize,
|
||||
inQualifierData);
|
||||
break;
|
||||
};
|
||||
|
||||
return theAnswer;
|
||||
}
|
||||
|
||||
void BGM_Control::GetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
UInt32& outDataSize,
|
||||
void* outData) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioControlPropertyScope:
|
||||
// This property returns the scope that the control is attached to.
|
||||
ThrowIf(inDataSize < sizeof(AudioObjectPropertyScope),
|
||||
CAException(kAudioHardwareBadPropertySizeError),
|
||||
"BGM_Control::GetPropertyData: not enough space for the return value of "
|
||||
"kAudioControlPropertyScope for the control");
|
||||
*reinterpret_cast<AudioObjectPropertyScope*>(outData) = mScope;
|
||||
outDataSize = sizeof(AudioObjectPropertyScope);
|
||||
break;
|
||||
|
||||
case kAudioControlPropertyElement:
|
||||
// This property returns the element that the control is attached to.
|
||||
ThrowIf(inDataSize < sizeof(AudioObjectPropertyElement),
|
||||
CAException(kAudioHardwareBadPropertySizeError),
|
||||
"BGM_Control::GetPropertyData: not enough space for the return value of "
|
||||
"kAudioControlPropertyElement for the control");
|
||||
*reinterpret_cast<AudioObjectPropertyElement*>(outData) = mElement;
|
||||
outDataSize = sizeof(AudioObjectPropertyElement);
|
||||
break;
|
||||
|
||||
default:
|
||||
BGM_Object::GetPropertyData(inObjectID,
|
||||
inClientPID,
|
||||
inAddress,
|
||||
inQualifierDataSize,
|
||||
inQualifierData,
|
||||
inDataSize,
|
||||
outDataSize,
|
||||
outData);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void BGM_Control::CheckObjectID(AudioObjectID inObjectID) const
|
||||
{
|
||||
ThrowIf(inObjectID == kAudioObjectUnknown || inObjectID != GetObjectID(),
|
||||
CAException(kAudioHardwareBadObjectError),
|
||||
"BGM_Control::CheckObjectID: wrong audio object ID for the control");
|
||||
}
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
87
BGMDriver/BGMDriver/BGM_Control.h
Normal file
87
BGMDriver/BGMDriver/BGM_Control.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGM_Control.h
|
||||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
//
|
||||
// An AudioObject that represents a user-controllable aspect of a device or stream, such as volume
|
||||
// or balance.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef BGMDriver__BGM_Control
|
||||
#define BGMDriver__BGM_Control
|
||||
|
||||
// Superclass Includes
|
||||
#include "BGM_Object.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
class BGM_Control
|
||||
:
|
||||
public BGM_Object
|
||||
{
|
||||
|
||||
protected:
|
||||
BGM_Control(AudioObjectID inObjectID,
|
||||
AudioClassID inClassID,
|
||||
AudioClassID inBaseClassID,
|
||||
AudioObjectID inOwnerObjectID,
|
||||
AudioObjectPropertyScope inScope,
|
||||
AudioObjectPropertyElement inElement
|
||||
= kAudioObjectPropertyElementMaster);
|
||||
|
||||
#pragma mark Property Operations
|
||||
|
||||
public:
|
||||
virtual bool HasProperty(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const;
|
||||
virtual bool IsPropertySettable(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const;
|
||||
virtual UInt32 GetPropertyDataSize(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData) const;
|
||||
virtual void GetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
UInt32& outDataSize,
|
||||
void* outData) const;
|
||||
|
||||
#pragma mark Implementation
|
||||
|
||||
protected:
|
||||
void CheckObjectID(AudioObjectID inObjectID) const;
|
||||
|
||||
protected:
|
||||
const AudioObjectPropertyScope mScope;
|
||||
const AudioObjectPropertyElement mElement;
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#endif /* BGMDriver__BGM_Control */
|
||||
|
File diff suppressed because it is too large
Load diff
|
@ -35,7 +35,10 @@
|
|||
#include "BGM_WrappedAudioEngine.h"
|
||||
#include "BGM_Clients.h"
|
||||
#include "BGM_TaskQueue.h"
|
||||
#include "BGM_AudibleState.h"
|
||||
#include "BGM_Stream.h"
|
||||
#include "BGM_VolumeControl.h"
|
||||
#include "BGM_MuteControl.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CAMutex.h"
|
||||
|
@ -55,12 +58,26 @@ class BGM_Device
|
|||
|
||||
public:
|
||||
static BGM_Device& GetInstance();
|
||||
static BGM_Device& GetUISoundsInstance();
|
||||
|
||||
private:
|
||||
static void StaticInitializer();
|
||||
|
||||
protected:
|
||||
BGM_Device();
|
||||
BGM_Device(AudioObjectID inObjectID,
|
||||
const CFStringRef __nonnull inDeviceName,
|
||||
const CFStringRef __nonnull inDeviceUID,
|
||||
const CFStringRef __nonnull inDeviceModelUID,
|
||||
AudioObjectID inInputStreamID,
|
||||
AudioObjectID inOutputStreamID);
|
||||
BGM_Device(AudioObjectID inObjectID,
|
||||
const CFStringRef __nonnull inDeviceName,
|
||||
const CFStringRef __nonnull inDeviceUID,
|
||||
const CFStringRef __nonnull inDeviceModelUID,
|
||||
AudioObjectID inInputStreamID,
|
||||
AudioObjectID inOutputStreamID,
|
||||
AudioObjectID inOutputVolumeControlID,
|
||||
AudioObjectID inOutputMuteControlID);
|
||||
virtual ~BGM_Device();
|
||||
|
||||
virtual void Activate();
|
||||
|
@ -87,15 +104,6 @@ private:
|
|||
void Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* __nonnull outData) const;
|
||||
void Device_SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData, UInt32 inDataSize, const void* __nonnull inData);
|
||||
|
||||
#pragma mark Control Property Operations
|
||||
|
||||
private:
|
||||
bool Control_HasProperty(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;
|
||||
bool Control_IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress) const;
|
||||
UInt32 Control_GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData) const;
|
||||
void Control_GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* __nonnull outData) const;
|
||||
void Control_SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* __nullable inQualifierData, UInt32 inDataSize, const void* __nonnull inData);
|
||||
|
||||
#pragma mark IO Operations
|
||||
|
||||
public:
|
||||
|
@ -113,10 +121,6 @@ private:
|
|||
void ReadInputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, void* __nonnull outBuffer);
|
||||
void WriteOutputData(UInt32 inIOBufferFrameSize, Float64 inSampleTime, const void* __nonnull inBuffer);
|
||||
void ApplyClientRelativeVolume(UInt32 inClientID, UInt32 inIOBufferFrameSize, void* __nonnull inBuffer) const;
|
||||
bool BufferIsAudible(UInt32 inIOBufferFrameSize, const void* __nonnull inBuffer);
|
||||
void UpdateAudibleStateSampleTimes_PreMix(UInt32 inClientID, UInt32 inIOBufferFrameSize, Float64 inOutputSampleTime, const void* __nonnull inBuffer);
|
||||
void UpdateAudibleStateSampleTimes_PostMix(UInt32 inIOBufferFrameSize, Float64 inOutputSampleTime, const void* __nonnull inBuffer);
|
||||
void UpdateDeviceAudibleState(UInt32 inIOBufferFrameSize, Float64 inOutputSampleTime);
|
||||
|
||||
#pragma mark Accessors
|
||||
|
||||
|
@ -133,6 +137,22 @@ public:
|
|||
void RequestSampleRate(Float64 inRequestedSampleRate);
|
||||
|
||||
private:
|
||||
/*!
|
||||
@return The Audio Object that has the ID inObjectID and belongs to this device.
|
||||
@throws CAException if there is no such Audio Object.
|
||||
*/
|
||||
const BGM_Object& GetOwnedObjectByID(AudioObjectID inObjectID) const;
|
||||
BGM_Object& GetOwnedObjectByID(AudioObjectID inObjectID);
|
||||
|
||||
/*! @return The number of Audio Objects belonging to this device, e.g. streams and controls. */
|
||||
UInt32 GetNumberOfSubObjects() const;
|
||||
/*! @return The number of Audio Objects with output scope belonging to this device. */
|
||||
UInt32 GetNumberOfOutputSubObjects() const;
|
||||
/*!
|
||||
@return The number of control Audio Objects with output scope belonging to this device, e.g.
|
||||
output volume and mute controls.
|
||||
*/
|
||||
UInt32 GetNumberOfOutputControls() const;
|
||||
/*!
|
||||
Enable or disable the device's volume and/or mute controls.
|
||||
|
||||
|
@ -154,13 +174,7 @@ private:
|
|||
void SetSampleRate(Float64 inNewSampleRate);
|
||||
|
||||
/*! @return True if inObjectID is the ID of one of this device's streams. */
|
||||
bool IsStreamID(AudioObjectID inObjectID) const noexcept;
|
||||
/*!
|
||||
@return The stream that has the ID inObjectID and belongs to this device.
|
||||
@throws CAException if there is no such stream (i.e. if inObjectID is neither
|
||||
kObjectID_Stream_Input nor kObjectID_Stream_Output.)
|
||||
*/
|
||||
const BGM_Stream& GetStreamByID(AudioObjectID inObjectID) const;
|
||||
inline bool IsStreamID(AudioObjectID inObjectID) const noexcept;
|
||||
|
||||
#pragma mark Hardware Accessors
|
||||
|
||||
|
@ -172,15 +186,11 @@ private:
|
|||
Float64 _HW_GetSampleRate() const;
|
||||
kern_return_t _HW_SetSampleRate(Float64 inNewSampleRate);
|
||||
UInt32 _HW_GetRingBufferFrameSize() const;
|
||||
SInt32 _HW_GetVolumeControlValue(AudioObjectID inObjectID) const;
|
||||
kern_return_t _HW_SetVolumeControlValue(AudioObjectID inObjectID, SInt32 inNewControlValue);
|
||||
UInt32 _HW_GetMuteControlValue(AudioObjectID inObjectID) const;
|
||||
kern_return_t _HW_SetMuteControlValue(AudioObjectID inObjectID, UInt32 inValue);
|
||||
|
||||
#pragma mark Implementation
|
||||
|
||||
public:
|
||||
CFStringRef __nonnull CopyDeviceUID() const { return CFSTR(kBGMDeviceUID); }
|
||||
CFStringRef __nonnull CopyDeviceUID() const { return mDeviceUID; }
|
||||
void AddClient(const AudioServerPlugInClientInfo* __nonnull inClientInfo);
|
||||
void RemoveClient(const AudioServerPlugInClientInfo* __nonnull inClientInfo);
|
||||
/*!
|
||||
|
@ -194,25 +204,29 @@ public:
|
|||
private:
|
||||
static pthread_once_t sStaticInitializer;
|
||||
static BGM_Device* __nonnull sInstance;
|
||||
static BGM_Device* __nonnull sUISoundsInstance;
|
||||
|
||||
#define kDeviceName "Background Music"
|
||||
#define kDeviceName_UISounds "Background Music (UI Sounds)"
|
||||
#define kDeviceManufacturerName "Background Music contributors"
|
||||
|
||||
|
||||
const CFStringRef __nonnull mDeviceName;
|
||||
const CFStringRef __nonnull mDeviceUID;
|
||||
const CFStringRef __nonnull mDeviceModelUID;
|
||||
|
||||
enum
|
||||
{
|
||||
kNumberOfSubObjects = 4,
|
||||
// The number of global/output sub-objects varies because the controls can be disabled.
|
||||
kNumberOfInputSubObjects = 1,
|
||||
kNumberOfOutputSubObjects = 3,
|
||||
|
||||
|
||||
kNumberOfStreams = 2,
|
||||
kNumberOfInputStreams = 1,
|
||||
kNumberOfOutputStreams = 1
|
||||
};
|
||||
|
||||
|
||||
CAMutex mStateMutex;
|
||||
CAMutex mIOMutex;
|
||||
|
||||
UInt64 __unused mSampleRateShadow; // Currently unused.
|
||||
const Float64 kSampleRateDefault = 44100.0;
|
||||
// Before we can change sample rate, the host has to stop the device. The new sample rate is
|
||||
// stored here while it does.
|
||||
|
@ -236,15 +250,8 @@ private:
|
|||
|
||||
BGM_Stream mInputStream;
|
||||
BGM_Stream mOutputStream;
|
||||
|
||||
SInt32 mDeviceAudibleState;
|
||||
struct
|
||||
{
|
||||
Float64 latestAudibleNonMusic;
|
||||
Float64 latestSilent;
|
||||
Float64 latestAudibleMusic;
|
||||
Float64 latestSilentMusic;
|
||||
} mAudibleStateSampleTimes;
|
||||
|
||||
BGM_AudibleState mAudibleState;
|
||||
|
||||
enum class ChangeAction : UInt64
|
||||
{
|
||||
|
@ -252,25 +259,10 @@ private:
|
|||
SetEnabledControls
|
||||
};
|
||||
|
||||
// This volume range will be used when the BGMDevice isn't wrapping another device (or we fail to
|
||||
// get the range of the wrapped device for some reason).
|
||||
#define kDefaultMinRawVolumeValue 0
|
||||
#define kDefaultMaxRawVolumeValue 96
|
||||
#define kDefaultMinDbVolumeValue -96.0f
|
||||
#define kDefaultMaxDbVolumeValue 0.0f
|
||||
|
||||
bool mOutputVolumeControlEnabled = true;
|
||||
bool mOutputMuteControlEnabled = true;
|
||||
BGM_VolumeControl mVolumeControl;
|
||||
BGM_MuteControl mMuteControl;
|
||||
bool mPendingOutputVolumeControlEnabled = true;
|
||||
bool mPendingOutputMuteControlEnabled = true;
|
||||
|
||||
SInt32 mOutputMasterVolumeControlRawValueShadow;
|
||||
SInt32 mOutputMasterMinRawVolumeShadow;
|
||||
SInt32 mOutputMasterMaxRawVolumeShadow;
|
||||
Float32 mOutputMasterMinDbVolumeShadow;
|
||||
Float32 mOutputMasterMaxDbVolumeShadow;
|
||||
CAVolumeCurve mVolumeCurve;
|
||||
UInt32 mOutputMuteValueShadow;
|
||||
|
||||
};
|
||||
|
||||
|
|
225
BGMDriver/BGMDriver/BGM_MuteControl.cpp
Normal file
225
BGMDriver/BGMDriver/BGM_MuteControl.cpp
Normal file
|
@ -0,0 +1,225 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGM_MuteControl.cpp
|
||||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "BGM_MuteControl.h"
|
||||
|
||||
// Local Includes
|
||||
#include "BGM_PlugIn.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CADebugMacros.h"
|
||||
#include "CAException.h"
|
||||
#include "CADispatchQueue.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
#pragma mark Construction/Destruction
|
||||
|
||||
BGM_MuteControl::BGM_MuteControl(AudioObjectID inObjectID,
|
||||
AudioObjectID inOwnerObjectID,
|
||||
AudioObjectPropertyScope inScope,
|
||||
AudioObjectPropertyElement inElement)
|
||||
:
|
||||
BGM_Control(inObjectID,
|
||||
kAudioMuteControlClassID,
|
||||
kAudioBooleanControlClassID,
|
||||
inOwnerObjectID,
|
||||
inScope,
|
||||
inElement),
|
||||
mMutex("Mute Control"),
|
||||
mMuted(false)
|
||||
{
|
||||
}
|
||||
|
||||
#pragma mark Property Operations
|
||||
|
||||
bool BGM_MuteControl::HasProperty(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
bool theAnswer = false;
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioBooleanControlPropertyValue:
|
||||
theAnswer = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
theAnswer = BGM_Control::HasProperty(inObjectID, inClientPID, inAddress);
|
||||
break;
|
||||
};
|
||||
|
||||
return theAnswer;
|
||||
}
|
||||
|
||||
bool BGM_MuteControl::IsPropertySettable(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
bool theAnswer = false;
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioBooleanControlPropertyValue:
|
||||
theAnswer = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
theAnswer = BGM_Control::IsPropertySettable(inObjectID, inClientPID, inAddress);
|
||||
break;
|
||||
};
|
||||
|
||||
return theAnswer;
|
||||
}
|
||||
|
||||
UInt32 BGM_MuteControl::GetPropertyDataSize(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
UInt32 theAnswer = 0;
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioBooleanControlPropertyValue:
|
||||
theAnswer = sizeof(UInt32);
|
||||
break;
|
||||
|
||||
default:
|
||||
theAnswer = BGM_Control::GetPropertyDataSize(inObjectID,
|
||||
inClientPID,
|
||||
inAddress,
|
||||
inQualifierDataSize,
|
||||
inQualifierData);
|
||||
break;
|
||||
};
|
||||
|
||||
return theAnswer;
|
||||
}
|
||||
|
||||
void BGM_MuteControl::GetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
UInt32& outDataSize,
|
||||
void* outData) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioBooleanControlPropertyValue:
|
||||
// This returns the mute value of the control.
|
||||
{
|
||||
ThrowIf(inDataSize < sizeof(UInt32),
|
||||
CAException(kAudioHardwareBadPropertySizeError),
|
||||
"BGM_MuteControl::GetPropertyData: not enough space for the return value "
|
||||
"of kAudioBooleanControlPropertyValue for the mute control");
|
||||
|
||||
CAMutex::Locker theLocker(mMutex);
|
||||
|
||||
// Non-zero for true, which means audio is being muted.
|
||||
*reinterpret_cast<UInt32*>(outData) = mMuted ? 1 : 0;
|
||||
outDataSize = sizeof(UInt32);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BGM_Control::GetPropertyData(inObjectID,
|
||||
inClientPID,
|
||||
inAddress,
|
||||
inQualifierDataSize,
|
||||
inQualifierData,
|
||||
inDataSize,
|
||||
outDataSize,
|
||||
outData);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void BGM_MuteControl::SetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
const void* inData)
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioBooleanControlPropertyValue:
|
||||
{
|
||||
ThrowIf(inDataSize < sizeof(UInt32),
|
||||
CAException(kAudioHardwareBadPropertySizeError),
|
||||
"BGM_MuteControl::SetPropertyData: wrong size for the data for "
|
||||
"kAudioBooleanControlPropertyValue");
|
||||
|
||||
CAMutex::Locker theLocker(mMutex);
|
||||
|
||||
// Non-zero for true, meaning audio will be muted.
|
||||
bool theNewMuted = (*reinterpret_cast<const UInt32*>(inData) != 0);
|
||||
|
||||
if(mMuted != theNewMuted)
|
||||
{
|
||||
mMuted = theNewMuted;
|
||||
|
||||
// Send notifications.
|
||||
CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{
|
||||
AudioObjectPropertyAddress theChangedProperty[1];
|
||||
theChangedProperty[0] = {
|
||||
kAudioBooleanControlPropertyValue, mScope, mElement
|
||||
};
|
||||
|
||||
BGM_PlugIn::Host_PropertiesChanged(inObjectID, 1, theChangedProperty);
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BGM_Control::SetPropertyData(inObjectID,
|
||||
inClientPID,
|
||||
inAddress,
|
||||
inQualifierDataSize,
|
||||
inQualifierData,
|
||||
inDataSize,
|
||||
inData);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
97
BGMDriver/BGMDriver/BGM_MuteControl.h
Normal file
97
BGMDriver/BGMDriver/BGM_MuteControl.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGM_MuteControl.h
|
||||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
//
|
||||
|
||||
#ifndef BGMDriver__BGM_MuteControl
|
||||
#define BGMDriver__BGM_MuteControl
|
||||
|
||||
// Superclass Includes
|
||||
#include "BGM_Control.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CAMutex.h"
|
||||
|
||||
// System Includes
|
||||
#include <MacTypes.h>
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
class BGM_MuteControl
|
||||
:
|
||||
public BGM_Control
|
||||
{
|
||||
|
||||
#pragma mark Construction/Destruction
|
||||
|
||||
public:
|
||||
BGM_MuteControl(AudioObjectID inObjectID,
|
||||
AudioObjectID inOwnerObjectID,
|
||||
AudioObjectPropertyScope inScope,
|
||||
AudioObjectPropertyElement inElement =
|
||||
kAudioObjectPropertyElementMaster);
|
||||
|
||||
#pragma mark Property Operations
|
||||
|
||||
bool HasProperty(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const;
|
||||
|
||||
bool IsPropertySettable(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const;
|
||||
|
||||
UInt32 GetPropertyDataSize(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData) const;
|
||||
|
||||
void GetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
UInt32& outDataSize,
|
||||
void* outData) const;
|
||||
|
||||
void SetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
const void* inData);
|
||||
|
||||
#pragma mark Implementation
|
||||
|
||||
private:
|
||||
CAMutex mMutex;
|
||||
bool mMuted;
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#endif /* BGMDriver__BGM_MuteControl */
|
||||
|
|
@ -381,6 +381,20 @@ void BGM_NullDevice::SetPropertyData(AudioObjectID inObjectID,
|
|||
inDataSize,
|
||||
inData);
|
||||
}
|
||||
else if(inObjectID == GetObjectID())
|
||||
{
|
||||
BGM_AbstractDevice::SetPropertyData(inObjectID,
|
||||
inClientPID,
|
||||
inAddress,
|
||||
inQualifierDataSize,
|
||||
inQualifierData,
|
||||
inDataSize,
|
||||
inData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw(CAException(kAudioHardwareBadObjectError));
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark IO Operations
|
||||
|
|
|
@ -101,7 +101,6 @@ bool BGM_Object::IsPropertySettable(AudioObjectID inObjectID, pid_t inClientPID,
|
|||
|
||||
default:
|
||||
Throw(CAException(kAudioHardwareUnknownPropertyError));
|
||||
break;
|
||||
};
|
||||
return theAnswer;
|
||||
}
|
||||
|
@ -128,7 +127,6 @@ UInt32 BGM_Object::GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientP
|
|||
|
||||
default:
|
||||
Throw(CAException(kAudioHardwareUnknownPropertyError));
|
||||
break;
|
||||
};
|
||||
return theAnswer;
|
||||
}
|
||||
|
@ -170,7 +168,6 @@ void BGM_Object::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, co
|
|||
|
||||
default:
|
||||
Throw(CAException(kAudioHardwareUnknownPropertyError));
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -182,7 +179,6 @@ void BGM_Object::SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, co
|
|||
{
|
||||
default:
|
||||
Throw(CAException(kAudioHardwareUnknownPropertyError));
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -140,7 +140,9 @@ UInt32 BGM_PlugIn::GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientP
|
|||
|
||||
case kAudioObjectPropertyOwnedObjects:
|
||||
case kAudioPlugInPropertyDeviceList:
|
||||
theAnswer = (BGM_NullDevice::GetInstance().IsActive() ? 2 : 1) * sizeof(AudioObjectID);
|
||||
// The plug-in owns the main BGM_Device, the instance of BGM_Device that handles UI
|
||||
// sounds and, if it's enabled, the null device.
|
||||
theAnswer = (BGM_NullDevice::GetInstance().IsActive() ? 3 : 2) * sizeof(AudioObjectID);
|
||||
break;
|
||||
|
||||
case kAudioPlugInPropertyTranslateUIDToDevice:
|
||||
|
@ -181,12 +183,32 @@ void BGM_PlugIn::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, co
|
|||
case kAudioPlugInPropertyDeviceList:
|
||||
{
|
||||
AudioObjectID* theReturnedDeviceList = reinterpret_cast<AudioObjectID*>(outData);
|
||||
if((inDataSize >= 2 * sizeof(AudioObjectID)) && BGM_NullDevice::GetInstance().IsActive())
|
||||
if(inDataSize >= 3 * sizeof(AudioObjectID))
|
||||
{
|
||||
if(BGM_NullDevice::GetInstance().IsActive())
|
||||
{
|
||||
theReturnedDeviceList[0] = kObjectID_Device;
|
||||
theReturnedDeviceList[1] = kObjectID_Device_UI_Sounds;
|
||||
theReturnedDeviceList[2] = kObjectID_Device_Null;
|
||||
|
||||
// say how much we returned
|
||||
outDataSize = 3 * sizeof(AudioObjectID);
|
||||
}
|
||||
else
|
||||
{
|
||||
theReturnedDeviceList[0] = kObjectID_Device;
|
||||
theReturnedDeviceList[1] = kObjectID_Device_UI_Sounds;
|
||||
|
||||
// say how much we returned
|
||||
outDataSize = 2 * sizeof(AudioObjectID);
|
||||
}
|
||||
}
|
||||
else if(inDataSize >= 2 * sizeof(AudioObjectID))
|
||||
{
|
||||
theReturnedDeviceList[0] = kObjectID_Device;
|
||||
theReturnedDeviceList[1] = kObjectID_Device_Null;
|
||||
|
||||
// say how much we returned
|
||||
theReturnedDeviceList[1] = kObjectID_Device_UI_Sounds;
|
||||
|
||||
// say how much we returned
|
||||
outDataSize = 2 * sizeof(AudioObjectID);
|
||||
}
|
||||
else if(inDataSize >= sizeof(AudioObjectID))
|
||||
|
@ -218,6 +240,12 @@ void BGM_PlugIn::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, co
|
|||
"kAudioPlugInPropertyTranslateUIDToDevice");
|
||||
*outID = kObjectID_Device;
|
||||
}
|
||||
else if(CFEqual(theUID, BGM_Device::GetUISoundsInstance().CopyDeviceUID()))
|
||||
{
|
||||
DebugMsg("BGM_PlugIn::GetPropertyData: Returning BGMUISoundsDevice for "
|
||||
"kAudioPlugInPropertyTranslateUIDToDevice");
|
||||
*outID = kObjectID_Device_UI_Sounds;
|
||||
}
|
||||
else if(BGM_NullDevice::GetInstance().IsActive() &&
|
||||
CFEqual(theUID, BGM_NullDevice::GetInstance().CopyDeviceUID()))
|
||||
{
|
||||
|
|
|
@ -98,6 +98,7 @@ static AudioServerPlugInDriverInterface* gAudioServerPlugInDriverInterfacePtr =
|
|||
static AudioServerPlugInDriverRef gAudioServerPlugInDriverRef = &gAudioServerPlugInDriverInterfacePtr;
|
||||
static UInt32 gAudioServerPlugInDriverRefCount = 1;
|
||||
|
||||
// TODO: This name is a bit misleading because the devices are actually owned by the plug-in.
|
||||
static BGM_Object& BGM_LookUpOwnerObject(AudioObjectID inObjectID)
|
||||
{
|
||||
switch(inObjectID)
|
||||
|
@ -112,6 +113,11 @@ static BGM_Object& BGM_LookUpOwnerObject(AudioObjectID inObjectID)
|
|||
case kObjectID_Mute_Output_Master:
|
||||
return BGM_Device::GetInstance();
|
||||
|
||||
case kObjectID_Device_UI_Sounds:
|
||||
case kObjectID_Stream_Input_UI_Sounds:
|
||||
case kObjectID_Stream_Output_UI_Sounds:
|
||||
return BGM_Device::GetUISoundsInstance();
|
||||
|
||||
case kObjectID_Device_Null:
|
||||
case kObjectID_Stream_Null:
|
||||
return BGM_NullDevice::GetInstance();
|
||||
|
@ -128,6 +134,9 @@ static BGM_AbstractDevice& BGM_LookUpDevice(AudioObjectID inObjectID)
|
|||
case kObjectID_Device:
|
||||
return BGM_Device::GetInstance();
|
||||
|
||||
case kObjectID_Device_UI_Sounds:
|
||||
return BGM_Device::GetUISoundsInstance();
|
||||
|
||||
case kObjectID_Device_Null:
|
||||
return BGM_NullDevice::GetInstance();
|
||||
}
|
||||
|
@ -276,6 +285,7 @@ static OSStatus BGM_Initialize(AudioServerPlugInDriverRef inDriver, AudioServerP
|
|||
|
||||
// Init/activate the devices.
|
||||
BGM_Device::GetInstance();
|
||||
BGM_Device::GetUISoundsInstance();
|
||||
BGM_NullDevice::GetInstance();
|
||||
}
|
||||
catch(const CAException& inException)
|
||||
|
@ -325,7 +335,7 @@ static OSStatus BGM_AddDeviceClient(AudioServerPlugInDriverRef inDriver, AudioOb
|
|||
ThrowIf(inDriver != gAudioServerPlugInDriverRef,
|
||||
CAException(kAudioHardwareBadObjectError),
|
||||
"BGM_AddDeviceClient: bad driver reference");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadObjectError),
|
||||
"BGM_AddDeviceClient: unknown device");
|
||||
|
||||
|
@ -361,7 +371,7 @@ static OSStatus BGM_RemoveDeviceClient(AudioServerPlugInDriverRef inDriver, Audi
|
|||
ThrowIf(inDriver != gAudioServerPlugInDriverRef,
|
||||
CAException(kAudioHardwareBadObjectError),
|
||||
"BGM_RemoveDeviceClient: bad driver reference");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadObjectError),
|
||||
"BGM_RemoveDeviceClient: unknown device");
|
||||
|
||||
|
@ -404,7 +414,7 @@ static OSStatus BGM_PerformDeviceConfigurationChange(AudioServerPlugInDriverRef
|
|||
ThrowIf(inDriver != gAudioServerPlugInDriverRef,
|
||||
CAException(kAudioHardwareBadObjectError),
|
||||
"BGM_PerformDeviceConfigurationChange: bad driver reference");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadDeviceError),
|
||||
"BGM_PerformDeviceConfigurationChange: unknown device");
|
||||
|
||||
|
@ -436,7 +446,7 @@ static OSStatus BGM_AbortDeviceConfigurationChange(AudioServerPlugInDriverRef in
|
|||
ThrowIf(inDriver != gAudioServerPlugInDriverRef,
|
||||
CAException(kAudioHardwareBadObjectError),
|
||||
"BGM_PerformDeviceConfigurationChange: bad driver reference");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadDeviceError),
|
||||
"BGM_PerformDeviceConfigurationChange: unknown device");
|
||||
|
||||
|
@ -667,7 +677,7 @@ static OSStatus BGM_StartIO(AudioServerPlugInDriverRef inDriver,
|
|||
ThrowIf(inDriver != gAudioServerPlugInDriverRef,
|
||||
CAException(kAudioHardwareBadObjectError),
|
||||
"BGM_StartIO: bad driver reference");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadDeviceError),
|
||||
"BGM_StartIO: unknown device");
|
||||
|
||||
|
@ -701,7 +711,7 @@ static OSStatus BGM_StopIO(AudioServerPlugInDriverRef inDriver,
|
|||
ThrowIf(inDriver != gAudioServerPlugInDriverRef,
|
||||
CAException(kAudioHardwareBadObjectError),
|
||||
"BGM_StopIO: bad driver reference");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadDeviceError),
|
||||
"BGM_StopIO: unknown device");
|
||||
|
||||
|
@ -751,7 +761,7 @@ static OSStatus BGM_GetZeroTimeStamp(AudioServerPlugInDriverRef inDriver,
|
|||
ThrowIfNULL(outSeed,
|
||||
CAException(kAudioHardwareIllegalOperationError),
|
||||
"BGM_GetZeroTimeStamp: no place to put the seed");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadDeviceError),
|
||||
"BGM_GetZeroTimeStamp: unknown device");
|
||||
|
||||
|
@ -794,7 +804,7 @@ static OSStatus BGM_WillDoIOOperation(AudioServerPlugInDriverRef inDriver,
|
|||
ThrowIfNULL(outWillDoInPlace,
|
||||
CAException(kAudioHardwareIllegalOperationError),
|
||||
"BGM_WillDoIOOperation: no place to put the in-place return value");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadDeviceError),
|
||||
"BGM_WillDoIOOperation: unknown device");
|
||||
|
||||
|
@ -839,7 +849,7 @@ static OSStatus BGM_BeginIOOperation(AudioServerPlugInDriverRef inDriver,
|
|||
ThrowIfNULL(inIOCycleInfo,
|
||||
CAException(kAudioHardwareIllegalOperationError),
|
||||
"BGM_BeginIOOperation: no cycle info");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadDeviceError),
|
||||
"BGM_BeginIOOperation: unknown device");
|
||||
|
||||
|
@ -887,7 +897,7 @@ static OSStatus BGM_DoIOOperation(AudioServerPlugInDriverRef inDriver,
|
|||
ThrowIfNULL(inIOCycleInfo,
|
||||
CAException(kAudioHardwareIllegalOperationError),
|
||||
"BGM_EndIOOperation: no cycle info");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadDeviceError),
|
||||
"BGM_EndIOOperation: unknown device");
|
||||
|
||||
|
@ -935,7 +945,7 @@ static OSStatus BGM_EndIOOperation(AudioServerPlugInDriverRef inDriver,
|
|||
ThrowIfNULL(inIOCycleInfo,
|
||||
CAException(kAudioHardwareIllegalOperationError),
|
||||
"BGM_EndIOOperation: no cycle info");
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_Null,
|
||||
ThrowIf(inDeviceObjectID != kObjectID_Device && inDeviceObjectID != kObjectID_Device_UI_Sounds && inDeviceObjectID != kObjectID_Device_Null,
|
||||
CAException(kAudioHardwareBadDeviceError),
|
||||
"BGM_EndIOOperation: unknown device");
|
||||
|
||||
|
|
|
@ -157,10 +157,12 @@ void BGM_TaskQueue::QueueSync_SwapClientShadowMaps(BGM_ClientMap* inClientMap
|
|||
QueueSync(kBGMTaskSwapClientShadowMaps, /* inRunOnRealtimeThread = */ true, reinterpret_cast<UInt64>(inClientMap));
|
||||
}
|
||||
|
||||
void BGM_TaskQueue::QueueAsync_SendPropertyNotification(AudioObjectPropertySelector inProperty)
|
||||
void BGM_TaskQueue::QueueAsync_SendPropertyNotification(AudioObjectPropertySelector inProperty, AudioObjectID inDeviceID)
|
||||
{
|
||||
DebugMsg("BGM_TaskQueue::QueueAsync_SendPropertyNotification: Queueing property notification. inProperty=%u", inProperty);
|
||||
BGM_Task theTask(kBGMTaskSendPropertyNotification, /* inIsSync = */ false, inProperty);
|
||||
DebugMsg("BGM_TaskQueue::QueueAsync_SendPropertyNotification: Queueing property notification. inProperty=%u inDeviceID=%u",
|
||||
inProperty,
|
||||
inDeviceID);
|
||||
BGM_Task theTask(kBGMTaskSendPropertyNotification, /* inIsSync = */ false, inProperty, inDeviceID);
|
||||
QueueOnNonRealtimeThread(theTask);
|
||||
}
|
||||
|
||||
|
@ -468,7 +470,7 @@ bool BGM_TaskQueue::ProcessNonRealTimeThreadTask(BGM_Task* inTask)
|
|||
{
|
||||
AudioObjectPropertyAddress thePropertyAddress[] = {
|
||||
{ static_cast<UInt32>(inTask->GetArg1()), kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster } };
|
||||
BGM_PlugIn::Host_PropertiesChanged(kObjectID_Device, 1, thePropertyAddress);
|
||||
BGM_PlugIn::Host_PropertiesChanged(static_cast<AudioObjectID>(inTask->GetArg2()), 1, thePropertyAddress);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ public:
|
|||
|
||||
// Sends a property changed notification to the BGMDevice host. Assumes the scope and element are kAudioObjectPropertyScopeGlobal and
|
||||
// kAudioObjectPropertyElementMaster because currently those are the only ones we use.
|
||||
void QueueAsync_SendPropertyNotification(AudioObjectPropertySelector inProperty);
|
||||
void QueueAsync_SendPropertyNotification(AudioObjectPropertySelector inProperty, AudioObjectID inDeviceID);
|
||||
|
||||
// Set/unset a client's is-doing-IO flag
|
||||
|
||||
|
|
368
BGMDriver/BGMDriver/BGM_VolumeControl.cpp
Normal file
368
BGMDriver/BGMDriver/BGM_VolumeControl.cpp
Normal file
|
@ -0,0 +1,368 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGM_VolumeControl.cpp
|
||||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "BGM_VolumeControl.h"
|
||||
|
||||
// Local Includes
|
||||
#include "BGM_PlugIn.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CAException.h"
|
||||
#include "CADebugMacros.h"
|
||||
#include "CADispatchQueue.h"
|
||||
|
||||
// STL Includes
|
||||
#include <algorithm>
|
||||
|
||||
// System Includes
|
||||
#include <CoreAudio/AudioHardwareBase.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
#pragma mark Construction/Destruction
|
||||
|
||||
BGM_VolumeControl::BGM_VolumeControl(AudioObjectID inObjectID,
|
||||
AudioObjectID inOwnerObjectID,
|
||||
AudioObjectPropertyScope inScope,
|
||||
AudioObjectPropertyElement inElement)
|
||||
:
|
||||
BGM_Control(inObjectID,
|
||||
kAudioVolumeControlClassID,
|
||||
kAudioLevelControlClassID,
|
||||
inOwnerObjectID,
|
||||
inScope,
|
||||
inElement),
|
||||
mMutex("Volume Control"),
|
||||
mVolumeRaw(kDefaultMinRawVolume),
|
||||
mMinVolumeRaw(kDefaultMinRawVolume),
|
||||
mMaxVolumeRaw(kDefaultMaxRawVolume),
|
||||
mMinVolumeDb(kDefaultMinDbVolume),
|
||||
mMaxVolumeDb(kDefaultMaxDbVolume)
|
||||
{
|
||||
// Setup the volume curve with the one range
|
||||
mVolumeCurve.AddRange(mMinVolumeRaw, mMaxVolumeRaw, mMinVolumeDb, mMaxVolumeDb);
|
||||
}
|
||||
|
||||
#pragma mark Property Operations
|
||||
|
||||
bool BGM_VolumeControl::HasProperty(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
bool theAnswer = false;
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioLevelControlPropertyScalarValue:
|
||||
case kAudioLevelControlPropertyDecibelValue:
|
||||
case kAudioLevelControlPropertyDecibelRange:
|
||||
case kAudioLevelControlPropertyConvertScalarToDecibels:
|
||||
case kAudioLevelControlPropertyConvertDecibelsToScalar:
|
||||
theAnswer = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
theAnswer = BGM_Control::HasProperty(inObjectID, inClientPID, inAddress);
|
||||
break;
|
||||
};
|
||||
|
||||
return theAnswer;
|
||||
}
|
||||
|
||||
bool BGM_VolumeControl::IsPropertySettable(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
bool theAnswer = false;
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioLevelControlPropertyDecibelRange:
|
||||
case kAudioLevelControlPropertyConvertScalarToDecibels:
|
||||
case kAudioLevelControlPropertyConvertDecibelsToScalar:
|
||||
theAnswer = false;
|
||||
break;
|
||||
|
||||
case kAudioLevelControlPropertyScalarValue:
|
||||
case kAudioLevelControlPropertyDecibelValue:
|
||||
theAnswer = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
theAnswer = BGM_Control::IsPropertySettable(inObjectID, inClientPID, inAddress);
|
||||
break;
|
||||
};
|
||||
|
||||
return theAnswer;
|
||||
}
|
||||
|
||||
UInt32 BGM_VolumeControl::GetPropertyDataSize(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
UInt32 theAnswer = 0;
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
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 = BGM_Control::GetPropertyDataSize(inObjectID,
|
||||
inClientPID,
|
||||
inAddress,
|
||||
inQualifierDataSize,
|
||||
inQualifierData);
|
||||
break;
|
||||
};
|
||||
|
||||
return theAnswer;
|
||||
}
|
||||
|
||||
void BGM_VolumeControl::GetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
UInt32& outDataSize,
|
||||
void* outData) const
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioLevelControlPropertyScalarValue:
|
||||
// This returns the value of the control in the normalized range of 0 to 1.
|
||||
{
|
||||
ThrowIf(inDataSize < sizeof(Float32),
|
||||
CAException(kAudioHardwareBadPropertySizeError),
|
||||
"BGM_VolumeControl::GetPropertyData: not enough space for the return value "
|
||||
"of kAudioLevelControlPropertyScalarValue for the volume control");
|
||||
|
||||
CAMutex::Locker theLocker(mMutex);
|
||||
|
||||
*reinterpret_cast<Float32*>(outData) = mVolumeCurve.ConvertRawToScalar(mVolumeRaw);
|
||||
outDataSize = sizeof(Float32);
|
||||
}
|
||||
break;
|
||||
|
||||
case kAudioLevelControlPropertyDecibelValue:
|
||||
// This returns the dB value of the control.
|
||||
{
|
||||
ThrowIf(inDataSize < sizeof(Float32),
|
||||
CAException(kAudioHardwareBadPropertySizeError),
|
||||
"BGM_VolumeControl::GetPropertyData: not enough space for the return value "
|
||||
"of kAudioLevelControlPropertyDecibelValue for the volume control");
|
||||
|
||||
CAMutex::Locker theLocker(mMutex);
|
||||
|
||||
*reinterpret_cast<Float32*>(outData) = mVolumeCurve.ConvertRawToDB(mVolumeRaw);
|
||||
outDataSize = sizeof(Float32);
|
||||
}
|
||||
break;
|
||||
|
||||
case kAudioLevelControlPropertyDecibelRange:
|
||||
// This returns the dB range of the control.
|
||||
ThrowIf(inDataSize < sizeof(AudioValueRange),
|
||||
CAException(kAudioHardwareBadPropertySizeError),
|
||||
"BGM_VolumeControl::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),
|
||||
"BGM_VolumeControl::GetPropertyData: not enough space for the return value "
|
||||
"of kAudioLevelControlPropertyConvertScalarToDecibels for the volume "
|
||||
"control");
|
||||
|
||||
// clamp the value to be between 0 and 1
|
||||
Float32 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),
|
||||
"BGM_VolumeControl::GetPropertyData: not enough space for the return value "
|
||||
"of kAudioLevelControlPropertyConvertDecibelsToScalar for the volume "
|
||||
"control");
|
||||
|
||||
// clamp the value to be between mMinVolumeDb and mMaxVolumeDb
|
||||
Float32 theVolumeValue = *reinterpret_cast<Float32*>(outData);
|
||||
theVolumeValue = std::min(mMaxVolumeDb, std::max(mMinVolumeDb, theVolumeValue));
|
||||
|
||||
// do the conversion
|
||||
*reinterpret_cast<Float32*>(outData) =
|
||||
mVolumeCurve.ConvertDBToScalar(theVolumeValue);
|
||||
|
||||
// report how much we wrote
|
||||
outDataSize = sizeof(Float32);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BGM_Control::GetPropertyData(inObjectID,
|
||||
inClientPID,
|
||||
inAddress,
|
||||
inQualifierDataSize,
|
||||
inQualifierData,
|
||||
inDataSize,
|
||||
outDataSize,
|
||||
outData);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void BGM_VolumeControl::SetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
const void* inData)
|
||||
{
|
||||
CheckObjectID(inObjectID);
|
||||
|
||||
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),
|
||||
"BGM_VolumeControl::SetPropertyData: wrong size for the data for "
|
||||
"kAudioLevelControlPropertyScalarValue");
|
||||
|
||||
// Read the new scalar volume and clamp it.
|
||||
Float32 theNewVolumeScalar = *reinterpret_cast<const Float32*>(inData);
|
||||
theNewVolumeScalar = std::min(1.0f, std::max(0.0f, theNewVolumeScalar));
|
||||
|
||||
// Store the new volume.
|
||||
SInt32 theNewVolumeRaw = mVolumeCurve.ConvertScalarToRaw(theNewVolumeScalar);
|
||||
SetVolumeRaw(theNewVolumeRaw);
|
||||
}
|
||||
break;
|
||||
|
||||
case kAudioLevelControlPropertyDecibelValue:
|
||||
// For the dB value, we first convert it to a raw 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),
|
||||
"BGM_VolumeControl::SetPropertyData: wrong size for the data for "
|
||||
"kAudioLevelControlPropertyDecibelValue");
|
||||
|
||||
// Read the new volume in dB and clamp it.
|
||||
Float32 theNewVolumeDb = *reinterpret_cast<const Float32*>(inData);
|
||||
theNewVolumeDb =
|
||||
std::min(mMaxVolumeDb, std::max(mMinVolumeDb, theNewVolumeDb));
|
||||
|
||||
// Store the new volume.
|
||||
SInt32 theNewVolumeRaw = mVolumeCurve.ConvertDBToRaw(theNewVolumeDb);
|
||||
SetVolumeRaw(theNewVolumeRaw);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BGM_Control::SetPropertyData(inObjectID,
|
||||
inClientPID,
|
||||
inAddress,
|
||||
inQualifierDataSize,
|
||||
inQualifierData,
|
||||
inDataSize,
|
||||
inData);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void BGM_VolumeControl::SetVolumeRaw(SInt32 inNewVolumeRaw)
|
||||
{
|
||||
CAMutex::Locker theLocker(mMutex);
|
||||
|
||||
// Make sure the new raw value is in the proper range.
|
||||
inNewVolumeRaw = std::min(std::max(mMinVolumeRaw, inNewVolumeRaw), mMaxVolumeRaw);
|
||||
|
||||
// Store the new volume.
|
||||
if(mVolumeRaw != inNewVolumeRaw)
|
||||
{
|
||||
mVolumeRaw = inNewVolumeRaw;
|
||||
|
||||
// Send notifications.
|
||||
CADispatchQueue::GetGlobalSerialQueue().Dispatch(false, ^{
|
||||
AudioObjectPropertyAddress theChangedProperties[2];
|
||||
theChangedProperties[0] = { kAudioLevelControlPropertyScalarValue, mScope, mElement };
|
||||
theChangedProperties[1] = { kAudioLevelControlPropertyDecibelValue, mScope, mElement };
|
||||
|
||||
BGM_PlugIn::Host_PropertiesChanged(GetObjectID(), 2, theChangedProperties);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
105
BGMDriver/BGMDriver/BGM_VolumeControl.h
Normal file
105
BGMDriver/BGMDriver/BGM_VolumeControl.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGM_VolumeControl.h
|
||||
// BGMDriver
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
//
|
||||
|
||||
#ifndef BGMDriver__BGM_VolumeControl
|
||||
#define BGMDriver__BGM_VolumeControl
|
||||
|
||||
// Superclass Includes
|
||||
#include "BGM_Control.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CAVolumeCurve.h"
|
||||
#include "CAMutex.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
class BGM_VolumeControl
|
||||
:
|
||||
public BGM_Control
|
||||
{
|
||||
|
||||
#pragma mark Construction/Destruction
|
||||
|
||||
public:
|
||||
BGM_VolumeControl(AudioObjectID inObjectID,
|
||||
AudioObjectID inOwnerObjectID,
|
||||
AudioObjectPropertyScope inScope,
|
||||
AudioObjectPropertyElement inElement =
|
||||
kAudioObjectPropertyElementMaster);
|
||||
|
||||
#pragma mark Property Operations
|
||||
|
||||
virtual bool HasProperty(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const;
|
||||
virtual bool IsPropertySettable(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress) const;
|
||||
virtual UInt32 GetPropertyDataSize(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData) const;
|
||||
virtual void GetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
UInt32& outDataSize,
|
||||
void* outData) const;
|
||||
virtual void SetPropertyData(AudioObjectID inObjectID,
|
||||
pid_t inClientPID,
|
||||
const AudioObjectPropertyAddress& inAddress,
|
||||
UInt32 inQualifierDataSize,
|
||||
const void* inQualifierData,
|
||||
UInt32 inDataSize,
|
||||
const void* inData);
|
||||
|
||||
#pragma mark Implementation
|
||||
|
||||
protected:
|
||||
void SetVolumeRaw(SInt32 inNewVolumeRaw);
|
||||
|
||||
private:
|
||||
const SInt32 kDefaultMinRawVolume = 0;
|
||||
const SInt32 kDefaultMaxRawVolume = 96;
|
||||
const Float32 kDefaultMinDbVolume = -96.0f;
|
||||
const Float32 kDefaultMaxDbVolume = 0.0f;
|
||||
|
||||
CAMutex mMutex;
|
||||
|
||||
SInt32 mVolumeRaw;
|
||||
SInt32 mMinVolumeRaw;
|
||||
SInt32 mMaxVolumeRaw;
|
||||
Float32 mMinVolumeDb;
|
||||
Float32 mMaxVolumeDb;
|
||||
|
||||
CAVolumeCurve mVolumeCurve;
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#endif /* BGMDriver__BGM_VolumeControl */
|
||||
|
|
@ -31,7 +31,7 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
// On failure, returns one of the kBGMXPC_* error codes, or the error code received from BGMXPCHelper. Returns kBGMXPC_Success otherwise.
|
||||
UInt64 StartBGMAppPlayThroughSync(void);
|
||||
UInt64 StartBGMAppPlayThroughSync(bool inIsForUISoundsDevice);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ static NSXPCConnection* CreateXPCHelperConnection()
|
|||
return theConnection;
|
||||
}
|
||||
|
||||
UInt64 StartBGMAppPlayThroughSync()
|
||||
UInt64 StartBGMAppPlayThroughSync(bool inIsForUISoundsDevice)
|
||||
{
|
||||
__block UInt64 theAnswer = kBGMXPC_Success;
|
||||
|
||||
|
@ -113,7 +113,7 @@ UInt64 StartBGMAppPlayThroughSync()
|
|||
|
||||
// Tell the enclosing function it can return now.
|
||||
dispatch_semaphore_signal(theReplySemaphore);
|
||||
}];
|
||||
} forUISoundsDevice:inIsForUISoundsDevice];
|
||||
|
||||
DebugMsg("BGM_XPCHelper::StartBGMAppPlayThroughSync: Waiting for BGMApp to tell us the output device is ready for IO");
|
||||
|
||||
|
|
|
@ -36,8 +36,9 @@
|
|||
|
||||
#pragma mark Construction/Destruction
|
||||
|
||||
BGM_Clients::BGM_Clients(BGM_TaskQueue* inTaskQueue)
|
||||
BGM_Clients::BGM_Clients(AudioObjectID inOwnerDeviceID, BGM_TaskQueue* inTaskQueue)
|
||||
:
|
||||
mOwnerDeviceID(inOwnerDeviceID),
|
||||
mClientMap(inTaskQueue)
|
||||
{
|
||||
mRelativeVolumeCurve.AddRange(kAppRelativeVolumeMinRawValue,
|
||||
|
@ -110,9 +111,10 @@ bool BGM_Clients::StartIONonRT(UInt32 inClientID)
|
|||
// Make sure we can start
|
||||
ThrowIf(mStartCount == UINT64_MAX, CAException(kAudioHardwareIllegalOperationError), "BGM_Clients::StartIO: failed to start because the ref count was maxxed out already");
|
||||
|
||||
DebugMsg("BGM_Clients::StartIO: Client %u (%s) starting IO",
|
||||
DebugMsg("BGM_Clients::StartIO: Client %u (%s, %d) starting IO",
|
||||
inClientID,
|
||||
CFStringGetCStringPtr(theClient.mBundleID.GetCFString(), kCFStringEncodingUTF8));
|
||||
CFStringGetCStringPtr(theClient.mBundleID.GetCFString(), kCFStringEncodingUTF8),
|
||||
theClient.mProcessID);
|
||||
|
||||
mClientMap.StartIONonRT(inClientID);
|
||||
|
||||
|
@ -160,9 +162,10 @@ bool BGM_Clients::StopIONonRT(UInt32 inClientID)
|
|||
|
||||
if(theClient.mDoingIO)
|
||||
{
|
||||
DebugMsg("BGM_Clients::StopIO: Client %u (%s) stopping IO",
|
||||
DebugMsg("BGM_Clients::StopIO: Client %u (%s, %d) stopping IO",
|
||||
inClientID,
|
||||
CFStringGetCStringPtr(theClient.mBundleID.GetCFString(), kCFStringEncodingUTF8));
|
||||
CFStringGetCStringPtr(theClient.mBundleID.GetCFString(), kCFStringEncodingUTF8),
|
||||
theClient.mProcessID);
|
||||
|
||||
mClientMap.StopIONonRT(inClientID);
|
||||
|
||||
|
@ -228,7 +231,7 @@ void BGM_Clients::SendIORunningNotifications(bool sendIsRunningNotification,
|
|||
theNotificationCount++;
|
||||
}
|
||||
|
||||
BGM_PlugIn::Host_PropertiesChanged(kObjectID_Device, theNotificationCount, theChangedProperties);
|
||||
BGM_PlugIn::Host_PropertiesChanged(mOwnerDeviceID, theNotificationCount, theChangedProperties);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ class BGM_Clients
|
|||
friend class BGM_ClientTasks;
|
||||
|
||||
public:
|
||||
BGM_Clients(BGM_TaskQueue* inTaskQueue);
|
||||
BGM_Clients(AudioObjectID inOwnerDeviceID, BGM_TaskQueue* inTaskQueue);
|
||||
~BGM_Clients() = default;
|
||||
// Disallow copying. (It could make sense to implement these in future, but we don't need them currently.)
|
||||
BGM_Clients(const BGM_Clients&) = delete;
|
||||
|
@ -111,6 +111,7 @@ public:
|
|||
bool SetClientsRelativeVolumes(const CACFArray inAppVolumes);
|
||||
|
||||
private:
|
||||
AudioObjectID mOwnerDeviceID;
|
||||
BGM_ClientMap mClientMap;
|
||||
|
||||
// Counters for the number of clients that are doing IO. These are used to tell whether any clients
|
||||
|
|
|
@ -57,7 +57,7 @@ static const AudioServerPlugInClientInfo client2Info = {
|
|||
- (void)setUp {
|
||||
[super setUp];
|
||||
|
||||
clients = new BGM_Clients(&taskQueue);
|
||||
clients = new BGM_Clients(kAudioObjectUnknown, &taskQueue);
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
|
|
|
@ -45,7 +45,7 @@ static NSString* kBGMXPCHelperMachServiceName = @kBGMXPCHelperBundleID;
|
|||
//
|
||||
// If BGMApp can be reached, the error it returns will be passed the reply block. Otherwise, the reply block will be passed an error with
|
||||
// one of the kBGMXPC_* error codes. It may have an underlying error using one of the NSXPCConnection* error codes from FoundationErrors.h.
|
||||
- (void) startBGMAppPlayThroughSyncWithReply:(void (^)(NSError*))reply;
|
||||
- (void) startBGMAppPlayThroughSyncWithReply:(void (^)(NSError*))reply forUISoundsDevice:(BOOL)isUI;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -53,7 +53,7 @@ static NSString* kBGMXPCHelperMachServiceName = @kBGMXPCHelperBundleID;
|
|||
// The protocol that BGMApp will vend as its XPC API.
|
||||
@protocol BGMAppXPCProtocol
|
||||
|
||||
- (void) startPlayThroughSyncWithReply:(void (^)(NSError*))reply;
|
||||
- (void) startPlayThroughSyncWithReply:(void (^)(NSError*))reply forUISoundsDevice:(BOOL)isUI;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -40,23 +40,32 @@ static const char* const kBGMIssueTrackerURL = "https://github.com/kyleneideck/B
|
|||
|
||||
#define kBGMDeviceUID "BGMDevice"
|
||||
#define kBGMDeviceModelUID "BGMDeviceModelUID"
|
||||
#define kBGMDeviceUID_UISounds "BGMDevice_UISounds"
|
||||
#define kBGMDeviceModelUID_UISounds "BGMDeviceModelUID_UISounds"
|
||||
#define kBGMNullDeviceUID "BGMNullDevice"
|
||||
#define kBGMNullDeviceModelUID "BGMNullDeviceModelUID"
|
||||
|
||||
// The object IDs for the audio objects this driver implements.
|
||||
//
|
||||
// BGMDevice always publishes this fixed set of objects (regardless of the wrapped device). We might need to
|
||||
// change that at some point, but so far it hasn't caused any problems and it makes the driver much simpler.
|
||||
// BGMDevice always publishes this fixed set of objects (except when BGMDevice's volume or mute
|
||||
// controls are disabled). We might need to change that at some point, but so far it hasn't caused
|
||||
// any problems and it makes the driver much simpler.
|
||||
enum
|
||||
{
|
||||
kObjectID_PlugIn = kAudioObjectPlugInObject,
|
||||
kObjectID_Device = 2, // Belongs to kObjectID_PlugIn
|
||||
kObjectID_Stream_Input = 3, // Belongs to kObjectID_Device
|
||||
kObjectID_Stream_Output = 4, // Belongs to kObjectID_Device
|
||||
kObjectID_Volume_Output_Master = 5, // Belongs to kObjectID_Device
|
||||
kObjectID_Mute_Output_Master = 6, // Belongs to kObjectID_Device
|
||||
kObjectID_Device_Null = 7, // Belongs to kObjectID_PlugIn
|
||||
kObjectID_Stream_Null = 8, // Belongs to kObjectID_Device_Null
|
||||
kObjectID_PlugIn = kAudioObjectPlugInObject,
|
||||
// BGMDevice
|
||||
kObjectID_Device = 2, // Belongs to kObjectID_PlugIn
|
||||
kObjectID_Stream_Input = 3, // Belongs to kObjectID_Device
|
||||
kObjectID_Stream_Output = 4, // Belongs to kObjectID_Device
|
||||
kObjectID_Volume_Output_Master = 5, // Belongs to kObjectID_Device
|
||||
kObjectID_Mute_Output_Master = 6, // Belongs to kObjectID_Device
|
||||
// Null Device
|
||||
kObjectID_Device_Null = 7, // Belongs to kObjectID_PlugIn
|
||||
kObjectID_Stream_Null = 8, // Belongs to kObjectID_Device_Null
|
||||
// BGMDevice for UI sounds
|
||||
kObjectID_Device_UI_Sounds = 9, // Belongs to kObjectID_PlugIn
|
||||
kObjectID_Stream_Input_UI_Sounds = 10, // Belongs to kObjectID_Device_UI_Sounds
|
||||
kObjectID_Stream_Output_UI_Sounds = 11, // Belongs to kObjectID_Device_UI_Sounds
|
||||
};
|
||||
|
||||
#pragma BGM Plug-in Custom Properties
|
||||
|
@ -104,9 +113,9 @@ enum
|
|||
};
|
||||
|
||||
// The number of silent/audible frames before BGMDriver will change kAudioDeviceCustomPropertyDeviceAudibleState
|
||||
#define kDeviceAudibleStateMinChangedFramesForUpdate (2 << 12)
|
||||
#define kDeviceAudibleStateMinChangedFramesForUpdate (2 << 11)
|
||||
|
||||
enum
|
||||
enum BGMDeviceAudibleState : SInt32
|
||||
{
|
||||
// kAudioDeviceCustomPropertyDeviceAudibleState values
|
||||
//
|
||||
|
|
Loading…
Reference in a new issue