6

I've been searching the documentation, mailing lists on and off for a couple of days but can't seem to find the answer to this.

I've got an OS X app that, amongst other things, queries the available hardware devices and their current volumes using kAudioDevicePropertyVolumeScalar and friends.

What I want to be able to do is get and set the -alert- volume (?) for the system output device represented by kAudioHardwarePropertyDefaultSystemOutputDevice rather than that devices volume.

To clarify from my limited understand, this is the volume setting users can adjust in System Preferences under 'Play sound effects through'.

Searching the coreaudio-api lists, I've managed to glean that this volume setting is not a device property but some kind of derived value, but I'm stumped as to where to from here.

Any help would be gratefully received.

Bijoy Thangaraj
  • 5,434
  • 4
  • 43
  • 70

2 Answers2

1

I am not sure if you really have a requirement for reading it through CoreAudio, but the following works just fine:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults addSuiteNamed:@"com.apple.systemsound"];        
NSLog(@"%f", [defaults floatForKey:@"com.apple.sound.beep.volume"]);

Though this might change with operating system updates as the settings are stored in ~/Library/Preferences/com.apple.systemsound.plist.

Nandeep Mali
  • 4,456
  • 1
  • 25
  • 34
1

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 AudioServicesPropertyIDs 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 UInt32s, 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

Teddy
  • 11
  • 3