element119
element119

Reputation: 7625

How to get the computer's current volume level?

How do I access the current volume level of a Mac from the Cocoa API?

For example: when I'm using Spotify.app on OS X 10.7 and a sound advertisement comes up, and I turn down my Mac's volume, the app will pause the ad until I turn it back up to an average level. I find this incredibly obnoxious and a violation of user privacy, but somehow Spotify has found a way to do this.

Is there any way I can do this with Cocoa? I'm making an app where it might come in useful to warn the user if the volume is low.

Upvotes: 8

Views: 5482

Answers (2)

sbooth
sbooth

Reputation: 16976

There are two options available. The first step is to determine what device you'd like and get its ID. Assuming the default output device, the code will look something like:

AudioObjectPropertyAddress propertyAddress = { 
    kAudioHardwarePropertyDefaultOutputDevice, 
    kAudioObjectPropertyScopeGlobal, 
    kAudioObjectPropertyElementMaster 
};

AudioDeviceID deviceID;
UInt32 dataSize = sizeof(deviceID);
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, &deviceID);

if(kAudioHardwareNoError != result)
    // Handle the error

Next, you can use the kAudioHardwareServiceDeviceProperty_VirtualMasterVolume property to get the device's virtual master volume:

AudioObjectPropertyAddress propertyAddress = { 
    kAudioHardwareServiceDeviceProperty_VirtualMasterVolume, 
    kAudioDevicePropertyScopeOutput,
    kAudioObjectPropertyElementMaster 
};

if(!AudioHardwareServiceHasProperty(deviceID, &propertyAddress))
    // An error occurred

Float32 volume;
UInt32 dataSize = sizeof(volume);
OSStatus result = AudioHardwareServiceGetPropertyData(deviceID, &propertyAddress, 0, NULL, &dataSize, &volume);

if(kAudioHardwareNoError != result)
    // An error occurred

Alternatively, you can use kAudioDevicePropertyVolumeScalar to get the volume for a specific channel:

UInt32 channel = 1; // Channel 0  is master, if available
AudioObjectPropertyAddress propertyAddress = { 
    kAudioDevicePropertyVolumeScalar, 
    kAudioDevicePropertyScopeOutput,
    channel 
};

if(!AudioObjectHasProperty(deviceID, &propertyAddress))
    // An error occurred

Float32 volume;
UInt32 dataSize = sizeof(volume);
OSStatus result = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &dataSize, &volume);

if(kAudioHardwareNoError != result)
    // An error occurred

The difference between the two is explained in Apple's docs:

kAudioHardwareServiceDeviceProperty_VirtualMasterVolume

A Float32 value that represents the value of the volume control. The range for this property’s value is 0.0 (silence) through 1.0 (full level). The effect of this property depends on the hardware device associated with the HAL audio object. If the device has a master volume control, this property controls it. If the device has individual channel volume controls, this property applies to those identified by the device's preferred multichannel layout, or the preferred stereo pair if the device is stereo only. This control maintains relative balance between the channels it affects.

So it can be tricky to define exactly what a device's volume is, especially for multichannel devices with non-standard channel maps.

Upvotes: 12

Isaac
Isaac

Reputation: 10834

From CocoaDev, these class methods look like they should work, though it's not particularly Cocoa-like:

#import <AudioToolbox/AudioServices.h>

+(AudioDeviceID)defaultOutputDeviceID
{
    AudioDeviceID   outputDeviceID = kAudioObjectUnknown;

    // get output device device
    UInt32 propertySize = 0;
    OSStatus status = noErr;
    AudioObjectPropertyAddress propertyAOPA;
    propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
    propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
    propertyAOPA.mSelector = kAudioHardwarePropertyDefaultOutputDevice;

    if (!AudioHardwareServiceHasProperty(kAudioObjectSystemObject, &propertyAOPA))
    {
        NSLog(@"Cannot find default output device!");
        return outputDeviceID;
    }

    propertySize = sizeof(AudioDeviceID);

    status = AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject, &propertyAOPA, 0, NULL, &propertySize, &outputDeviceID);

    if(status) 
    {
        NSLog(@"Cannot find default output device!");
    }
    return outputDeviceID;
}

// getting system volume

+(float)volume 
{
    Float32         outputVolume;

    UInt32 propertySize = 0;
    OSStatus status = noErr;
    AudioObjectPropertyAddress propertyAOPA;
    propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
    propertyAOPA.mSelector = kAudioHardwareServiceDeviceProperty_VirtualMasterVolume;
    propertyAOPA.mScope = kAudioDevicePropertyScopeOutput;

    AudioDeviceID outputDeviceID = [[self class] defaultOutputDeviceID];

    if (outputDeviceID == kAudioObjectUnknown)
    {
        NSLog(@"Unknown device");
        return 0.0;
    }

    if (!AudioHardwareServiceHasProperty(outputDeviceID, &propertyAOPA))
    {
        NSLog(@"No volume returned for device 0x%0x", outputDeviceID);
        return 0.0;
    }

    propertySize = sizeof(Float32);

    status = AudioHardwareServiceGetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, &propertySize, &outputVolume);

    if (status)
    {
        NSLog(@"No volume returned for device 0x%0x", outputDeviceID);
        return 0.0;
    }

    if (outputVolume < 0.0 || outputVolume > 1.0) return 0.0;

    return outputVolume;
}

Upvotes: 2

Related Questions