TL;DR: I’ve published a reusable library to get the system alert volume: https://github.com/teddywing/SSVLSystemAlertVolume
As you pointed out, the system alert volume is different from the output device volume.
If we query the volume of the device associated with kAudioHardwarePropertyDefaultSystemOutputDevice
using the kAudioHardwareServiceDeviceProperty_VirtualMasterVolume
property (using a method similar to the one described in Technical Q&A QA1016), we get the volume of the device, not the system alert volume.
While kAudioHardwareServiceDeviceProperty_VirtualMasterVolume
is an Audio Hardware property, the system alert volume is an Audio Services property. This is the Audio Services property that represents the system alert volume:
const AudioServicesPropertyID kAudioServicesPropertySystemAlertVolume = 'ssvl';
From what I can tell, this Audio Services property is undocumented and/or private, as I haven’t been able to find it in any of the headers in MacOSX10.15.sdk/System/Library/Frameworks/
. I’m defining it here at the application level.
To get the system alert volume:
#include <AudioToolbox/AudioToolbox.h>
#include <CoreFoundation/CoreFoundation.h>
OSStatus system_volume_get(Float32 *volume) {
UInt32 volume_size = sizeof(*volume);
OSStatus result = AudioServicesGetProperty(
kAudioServicesPropertySystemAlertVolume,
0,
NULL,
&volume_size,
volume
);
if (*volume != 0) {
*volume = log(*volume) + 1.0;
}
else {
*volume = 0;
}
return result;
}
To set the alert volume:
OSStatus system_volume_set(Float32 volume) {
volume = exp(volume - 1.0);
return AudioServicesSetProperty(
kAudioServicesPropertySystemAlertVolume,
0,
NULL,
sizeof(volume),
&volume
);
}
Reverse engineering the Sound preference pane
To discover how to access the system alert volume, I disassembled the Sound preference pane binary using Hopper. On macOS 10.15, the binary lives at: /System/Library/PreferencePanes/Sound.prefPane/Contents/MacOS/Sound
.
The disassembly gives us two accessor methods for the property, which, after translating into C, yield the functions at the top of this answer:
/* @class AppleSound_SoundSettings */
-(float)alertSoundVolume {
var_8 = 0x4;
rax = AudioServicesGetProperty(0x7373766c, 0x0, 0x0, &var_8, &var_4);
if (rax != 0x0) {
xmm0 = *(float *)float_value_0_5;
}
else {
xmm1 = var_4;
xmm0 = 0x0;
if (xmm1 != xmm0 || !CPU_FLAGS & NP) {
log(intrinsic_cvtss2sd(0x0, xmm1));
xmm0 = intrinsic_cvtsd2ss(xmm0 + *double_value_1, xmm0 + *double_value_1);
}
}
return xmm0;
}
/* @class AppleSound_SoundSettings */
-(void)setAlertSoundVolume:(float)arg2 {
xmm0 = arg2;
if (xmm0 != 0x0 || !CPU_FLAGS & NP) {
exp(intrinsic_cvtss2sd(xmm0, xmm0) + *double_value_minus_1);
xmm0 = intrinsic_cvtsd2ss(xmm0 + *double_value_minus_1, xmm0 + *double_value_minus_1);
}
var_C = xmm0;
rax = AudioServicesSetProperty(0x7373766c, 0x0, 0x0, 0x4, &var_C);
if (rax != 0x0) {
NSLog(@"Error %d setting system sound volume", rax);
}
else {
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:*0x19f00 object:0x0 userInfo:0x0 options:0x3];
}
return;
}
Where the AudioServicesPropertyID
comes from
We can see in the disassembled accessors above that the property is accessed with AudioServicesGetProperty()
and AudioServicesSetProperty()
, which are defined in “AudioServices.h” in “AudioToolbox.framework”. Those functions are called with the AudioServicesPropertyID
0x7373766c
as their first argument. That AudioServicesPropertyID
is the key to getting the system alert volume.
Only two AudioServicesPropertyID
s are defined in “AudioToolbox.framework” (in MacOSX10.15.sdk):
// AudioToolbox.framework/Versions/A/Headers/AudioServices.h
typedef UInt32 AudioServicesPropertyID;
// ...
CF_ENUM(AudioServicesPropertyID)
{
kAudioServicesPropertyIsUISound = 'isui',
kAudioServicesPropertyCompletePlaybackIfAppDies = 'ifdi'
};
Neither of the above properties are equal to the system alert volume property, 0x7373766c
.
Their values are UInt32
s, or really something called a FourCharCode
:
// CoreFoundation.framework/Versions/A/Headers/CFBase.h
typedef UInt32 FourCharCode;
The answers to the following question describe ways to convert an integer to a FourCharCode
: iOS/C: Convert "integer" into four character string.
Using one of the methods described in the above question, we can convert 0x7373766c
to a FourCharCode
, which results in 'ssvl'
.
Logarithm and exponent
I haven’t been able to work out why the alert volume uses logarithms and exponents, but they’re required from what I can tell. If I set the alert volume to 0.5 without the exponent as in the following:
Float32 volume = 0.5;
AudioServicesSetProperty(
kAudioServicesPropertySystemAlertVolume,
0,
NULL,
sizeof(volume),
&volume
);
then the Sound preference pane shows a volume level of 31% instead of 50%:
System alert volume set to 31% of output volume