// static bool BGMDeviceControlSync::SetVirtualMasterVolume(CAHALAudioDevice inDevice, AudioObjectPropertyScope inScope, Float32 inVolume) { // TODO: For me, setting the virtual master volume sets all the device's channels to the same volume, meaning you can't // keep any channels quieter than the others. The expected behaviour is to scale the channel volumes // proportionally. So to do this properly I think we'd have to store BGMDevice's previous volume and calculate // each channel's new volume from its current volume and the distance between BGMDevice's old and new volumes. // // The docs kAudioHardwareServiceDeviceProperty_VirtualMasterVolume for say // "If the device has individual channel volume controls, this property will apply to those identified by the // device's preferred multi-channel layout (or preferred stereo pair if the device is stereo only). Note that // this control maintains the relative balance between all the channels it affects. // so I'm not sure why that's not working here. As a workaround we take the to device's (virtual master) balance // before changing the volume and set it back after, but of course that'll only work for stereo devices. bool didSetVolume = false; AudioObjectPropertyAddress virtualMasterVolumeAddress = { kAudioHardwareServiceDeviceProperty_VirtualMasterVolume, inScope, kAudioObjectPropertyElementMaster }; bool hasVirtualMasterVolume = AudioHardwareServiceHasProperty(inDevice.GetObjectID(), &virtualMasterVolumeAddress); Boolean virtualMasterVolumeIsSettable; OSStatus err = AudioHardwareServiceIsPropertySettable(inDevice.GetObjectID(), &virtualMasterVolumeAddress, &virtualMasterVolumeIsSettable); virtualMasterVolumeIsSettable &= (err == kAudioServicesNoError); if(hasVirtualMasterVolume && virtualMasterVolumeIsSettable) { // Not sure why, but setting the virtual master volume sets all channels to the same volume. As a workaround, we store // the current balance here so we can reset it after setting the volume. Float32 virtualMasterBalance; bool didGetVirtualMasterBalance = GetVirtualMasterBalance(inDevice, inScope, virtualMasterBalance); didSetVolume = kAudioServicesNoError == AHSSetPropertyData(inDevice.GetObjectID(), &virtualMasterVolumeAddress, sizeof(Float32), &inVolume); // Reset the balance AudioObjectPropertyAddress virtualMasterBalanceAddress = { kAudioHardwareServiceDeviceProperty_VirtualMasterBalance, inScope, kAudioObjectPropertyElementMaster }; if(didSetVolume && didGetVirtualMasterBalance && AudioHardwareServiceHasProperty(inDevice.GetObjectID(), &virtualMasterBalanceAddress)) { Boolean balanceIsSettable; err = AudioHardwareServiceIsPropertySettable(inDevice.GetObjectID(), &virtualMasterBalanceAddress, &balanceIsSettable); if(err == kAudioServicesNoError && balanceIsSettable) { AHSSetPropertyData(inDevice.GetObjectID(), &virtualMasterBalanceAddress, sizeof(Float32), &virtualMasterBalance); } } } return didSetVolume; }
bool canSetVolume() { Boolean isSettable = NO; return AudioHardwareServiceIsPropertySettable (outputDeviceID, &addr, &isSettable) == noErr && isSettable; }
// setting system volume. Mutes if under threshhold. // Courtesy: https://gist.github.com/atr000/205140 int setVolume(float newVolume) { if (newVolume < 0.0 || newVolume > 1.0) { fprintf(stderr, "ERROR: Requested volume out of range (%.2f)\n", newVolume); return 1; } // get output device UInt32 propertySize = 0; OSStatus status = noErr; AudioObjectPropertyAddress propertyAOPA; propertyAOPA.mElement = kAudioObjectPropertyElementMaster; propertyAOPA.mScope = kAudioDevicePropertyScopeOutput; if (newVolume < 0.001) { propertyAOPA.mSelector = kAudioDevicePropertyMute; } else { propertyAOPA.mSelector = kAudioHardwareServiceDeviceProperty_VirtualMasterVolume; } AudioDeviceID outputDeviceID = defaultOutputDeviceID(); if (outputDeviceID == kAudioObjectUnknown) { fprintf(stderr, "ERROR: Unknown device\n"); return 2; } if (!AudioHardwareServiceHasProperty(outputDeviceID, &propertyAOPA)) { fprintf(stderr, "ERROR: Device 0x%0x does not support volume control\n", outputDeviceID); return 3; } Boolean canSetVolume = false; status = AudioHardwareServiceIsPropertySettable(outputDeviceID, &propertyAOPA, &canSetVolume); if (status || !canSetVolume) { fprintf(stderr, "ERROR: Device 0x%0x does not support volume control\n", outputDeviceID); return 4; } if (propertyAOPA.mSelector == kAudioDevicePropertyMute) { propertySize = sizeof(UInt32); UInt32 mute = 1; status = AudioHardwareServiceSetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, propertySize, &mute); } else { propertySize = sizeof(Float32); status = AudioHardwareServiceSetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, propertySize, &newVolume); if (status) { fprintf(stderr, "ERROR: Unable to set volume for device 0x%0x\n", outputDeviceID); return 5; } // make sure we're not muted propertyAOPA.mSelector = kAudioDevicePropertyMute; propertySize = sizeof(UInt32); UInt32 mute = 0; if (!AudioHardwareServiceHasProperty(outputDeviceID, &propertyAOPA)) { fprintf(stderr, "ERROR: Device 0x%0x does not support muting\n", outputDeviceID); return 6; } Boolean canSetMute = false; status = AudioHardwareServiceIsPropertySettable(outputDeviceID, &propertyAOPA, &canSetMute); if (status || !canSetMute) { fprintf(stderr, "ERROR: Device 0x%0x does not support muting\n", outputDeviceID); return 7; } status = AudioHardwareServiceSetPropertyData(outputDeviceID, &propertyAOPA, 0, NULL, propertySize, &mute); } if (status) { fprintf(stderr, "ERROR: Unable to set volume for device 0x%0x\n", outputDeviceID); return 8; } return 0; }