From acd499e2ebc8815c9a4e26d6393d52a62cfc1b81 Mon Sep 17 00:00:00 2001 From: Fred Emmott Date: Sat, 13 Feb 2021 10:12:54 -0600 Subject: [PATCH] Use new AudioDeviceLib project instead of embedding/copying C++ --- AudioDeviceLib.cmake | 40 +++ CMakeLists.txt | 1 + Sources/AudioDevices-inline.h | 45 --- Sources/AudioDevices.h | 115 ------- Sources/AudioDevicesMacOS.cpp | 406 ------------------------ Sources/AudioDevicesWindows.cpp | 434 -------------------------- Sources/AudioMuteStreamDeckPlugin.cpp | 2 +- Sources/BaseMuteAction.cpp | 2 +- Sources/CMakeLists.txt | 9 +- Sources/DefaultAudioDevices.cpp | 2 +- Sources/DefaultAudioDevices.h | 2 +- Sources/MuteAction.cpp | 2 +- Sources/StringEncoding.cpp | 35 --- Sources/StringEncoding.h | 14 - Sources/ToggleMuteAction.cpp | 2 +- Sources/UnmuteAction.cpp | 2 +- 16 files changed, 49 insertions(+), 1064 deletions(-) create mode 100644 AudioDeviceLib.cmake delete mode 100644 Sources/AudioDevices-inline.h delete mode 100644 Sources/AudioDevices.h delete mode 100644 Sources/AudioDevicesMacOS.cpp delete mode 100644 Sources/AudioDevicesWindows.cpp delete mode 100644 Sources/StringEncoding.cpp delete mode 100644 Sources/StringEncoding.h diff --git a/AudioDeviceLib.cmake b/AudioDeviceLib.cmake new file mode 100644 index 0000000..8e6d37b --- /dev/null +++ b/AudioDeviceLib.cmake @@ -0,0 +1,40 @@ +include(ExternalProject) + +ExternalProject_Add( + AudioDeviceLib_build + GIT_REPOSITORY https://github.com/fredemmott/AudioDeviceLib + GIT_TAG a782d113e941aa57ced3c62414ae15da2ba6649c + CMAKE_ARGS + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_MSVC_RUNTIME_LIBRARY=${CMAKE_MSVC_RUNTIME_LIBRARY} + -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} + -DCMAKE_INSTALL_PREFIX= +) + +ExternalProject_Get_Property( + AudioDeviceLib_build + INSTALL_DIR +) +add_library(AudioDeviceLib INTERFACE) +add_dependencies(AudioDeviceLib AudioDeviceLib_build) +target_link_libraries( + AudioDeviceLib + INTERFACE + ${INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}AudioDeviceLib${CMAKE_STATIC_LIBRARY_SUFFIX} +) +target_include_directories(AudioDeviceLib INTERFACE ${INSTALL_DIR}/include) + +if(APPLE) + find_library(AUDIOTOOLBOX_FRAMEWORK AudioToolbox) + find_library(COREAUDIO_FRAMEWORK CoreAudio) + find_library(COREFOUNDATION_FRAMEWORK CoreFoundation) + target_link_libraries( + AudioDeviceLib + INTERFACE + ${AUDIOTOOLBOX_FRAMEWORK} + ${COREAUDIO_FRAMEWORK} + ${COREFOUNDATION_FRAMEWORK} + ) +elseif(WIN32) + target_link_libraries(AudioDeviceLib INTERFACE Winmm) +endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index d615e4a..2fb2e69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ message(STATUS "Install dir: ${CMAKE_INSTALL_PREFIX}") include_directories("${CMAKE_SOURCE_DIR}") +include("AudioDeviceLib.cmake") include("StreamDeckSDK.cmake") include(sign_target.cmake) diff --git a/Sources/AudioDevices-inline.h b/Sources/AudioDevices-inline.h deleted file mode 100644 index ccab700..0000000 --- a/Sources/AudioDevices-inline.h +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright (c) 2019-present, Fred Emmott - * - * This source code is licensed under the MIT-style license found in the - * LICENSE file. - */ - -#pragma once - -#include - -namespace FredEmmott::Audio { - -template -class AudioDeviceCallbackHandle { - public: - AudioDeviceCallbackHandle(TImpl* impl) : mImpl(impl) { - } - ~AudioDeviceCallbackHandle() { - } - - AudioDeviceCallbackHandle(const AudioDeviceCallbackHandle& other) = delete; - AudioDeviceCallbackHandle& operator=(const AudioDeviceCallbackHandle& other) - = delete; - - private: - std::unique_ptr mImpl; -}; - -struct MuteCallbackHandleImpl; -class MuteCallbackHandle final - : public AudioDeviceCallbackHandle { - public: - MuteCallbackHandle(MuteCallbackHandleImpl* impl); - ~MuteCallbackHandle(); -}; - -struct DefaultChangeCallbackHandleImpl; -class DefaultChangeCallbackHandle final - : public AudioDeviceCallbackHandle { - public: - DefaultChangeCallbackHandle(DefaultChangeCallbackHandleImpl* impl); - ~DefaultChangeCallbackHandle(); -}; - -}// namespace FredEmmott::Audio diff --git a/Sources/AudioDevices.h b/Sources/AudioDevices.h deleted file mode 100644 index d7ce057..0000000 --- a/Sources/AudioDevices.h +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright (c) 2019-present, Fred Emmott - * - * This source code is licensed under the MIT-style license found in the - * LICENSE file. - */ - -#pragma once - -#include -#include -#include -#include - -namespace FredEmmott::Audio { - -class device_error : public std::runtime_error { - protected: - device_error(const char* what) : std::runtime_error(what) { - } -}; - -class operation_not_supported_error : public device_error { - public: - operation_not_supported_error() : device_error("Operation not supported") { - } -}; - -class device_not_available_error : public device_error { - public: - device_not_available_error() : device_error("Device not available") { - } -}; - -enum class AudioDeviceRole { - DEFAULT, - COMMUNICATION, -}; - -enum class AudioDeviceDirection { - OUTPUT, - INPUT, -}; - -enum class AudioDeviceState { - CONNECTED, - DEVICE_NOT_PRESENT,// USB device unplugged - DEVICE_DISABLED, - DEVICE_PRESENT_NO_CONNECTION,// device present, but nothing's plugged into it, - // e.g. headphone jack with nothing plugged in -}; - -struct AudioDeviceInfo { - std::string id; - std::string interfaceName;// e.g. "Generic USB Audio Device" - std::string endpointName;// e.g. "Speakers" - std::string displayName;// e.g. "Generic USB Audio Device (Speakers)" - AudioDeviceDirection direction; - AudioDeviceState state; -}; - -std::map GetAudioDeviceList(AudioDeviceDirection); -AudioDeviceState GetAudioDeviceState(const std::string& id); - -std::string GetDefaultAudioDeviceID(AudioDeviceDirection, AudioDeviceRole); -void SetDefaultAudioDeviceID( - AudioDeviceDirection, - AudioDeviceRole, - const std::string& deviceID); - -bool IsAudioDeviceMuted(const std::string& deviceID); -void MuteAudioDevice(const std::string& deviceID); -void UnmuteAudioDevice(const std::string& deviceID); - -template -class AudioDeviceCallbackHandle { - public: - AudioDeviceCallbackHandle(TImpl* impl) : mImpl(impl) { - } - ~AudioDeviceCallbackHandle() { - } - - AudioDeviceCallbackHandle(const AudioDeviceCallbackHandle& other) = delete; - AudioDeviceCallbackHandle& operator=(const AudioDeviceCallbackHandle& other) - = delete; - - private: - std::unique_ptr mImpl; -}; - -struct MuteCallbackHandleImpl; -class MuteCallbackHandle final - : public AudioDeviceCallbackHandle { - public: - MuteCallbackHandle(MuteCallbackHandleImpl* impl); - ~MuteCallbackHandle(); -}; - -std::unique_ptr AddAudioDeviceMuteUnmuteCallback( - const std::string& deviceID, - std::function); - -struct DefaultChangeCallbackHandleImpl; -class DefaultChangeCallbackHandle final - : public AudioDeviceCallbackHandle { - public: - DefaultChangeCallbackHandle(DefaultChangeCallbackHandleImpl* impl); - ~DefaultChangeCallbackHandle(); -}; - -std::unique_ptr - AddDefaultAudioDeviceChangeCallback( - std::function< - void(AudioDeviceDirection, AudioDeviceRole, const std::string&)>); - -}// namespace FredEmmott::Audio diff --git a/Sources/AudioDevicesMacOS.cpp b/Sources/AudioDevicesMacOS.cpp deleted file mode 100644 index 7cc4015..0000000 --- a/Sources/AudioDevicesMacOS.cpp +++ /dev/null @@ -1,406 +0,0 @@ -/* Copyright (c) 2019-present, Fred Emmott - * - * This source code is licensed under the MIT-style license found in the - * LICENSE file. - */ - -#include -#include -#include - -#include "AudioDevices.h" - -namespace FredEmmott::Audio { - -namespace { - -std::string Utf8StringFromCFString(CFStringRef ref, size_t buf_size = 1024) { - // Re-use the existing buffer if possible... - auto ptr = CFStringGetCStringPtr(ref, kCFStringEncodingUTF8); - if (ptr) { - return ptr; - } - // ... but sometimes it isn't. Copy. - char buf[buf_size]; - CFStringGetCString(ref, buf, buf_size, kCFStringEncodingUTF8); - return buf; -} - -template -T GetAudioObjectProperty( - AudioObjectID id, - const AudioObjectPropertyAddress& prop) { - T value; - UInt32 size = sizeof(value); - const auto result - = AudioObjectGetPropertyData(id, &prop, 0, nullptr, &size, &value); - switch (result) { - case kAudioHardwareBadDeviceError: - case kAudioHardwareBadObjectError: - throw device_not_available_error(); - case kAudioHardwareUnsupportedOperationError: - case kAudioHardwareUnknownPropertyError: { - char buf[5]; - buf[4] = 0; - memcpy(buf, &prop.mSelector, 4); - buf[0] ^= buf[3]; - buf[3] ^= buf[0]; - buf[0] ^= buf[3]; - buf[1] ^= buf[2]; - buf[2] ^= buf[1]; - buf[1] ^= buf[2]; - ESDLog("Get prop bad prop: {}", buf); - } - throw operation_not_supported_error(); - default: - break; - } - return value; -} - -template <> -bool GetAudioObjectProperty( - UInt32 id, - const AudioObjectPropertyAddress& prop) { - return GetAudioObjectProperty(id, prop); -} - -template <> -std::string GetAudioObjectProperty( - UInt32 id, - const AudioObjectPropertyAddress& prop) { - CFStringRef value = nullptr; - UInt32 size = sizeof(value); - AudioObjectGetPropertyData(id, &prop, 0, nullptr, &size, &value); - - if (!value) { - return std::string(); - } - auto ret = Utf8StringFromCFString(value); - CFRelease(value); - return ret; -} - -std::string MakeDeviceID(UInt32 id, AudioDeviceDirection dir) { - const auto uid = GetAudioObjectProperty( - id, {kAudioDevicePropertyDeviceUID, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}); - return fmt::format( - "{}/{}", dir == AudioDeviceDirection::INPUT ? "input" : "output", uid); -} - -std::tuple ParseDeviceID(const std::string& id) { - auto idx = id.find_first_of('/'); - auto direction = id.substr(0, idx) == "input" ? AudioDeviceDirection::INPUT - : AudioDeviceDirection::OUTPUT; - CFStringRef uid = CFStringCreateWithCString( - kCFAllocatorDefault, id.substr(idx + 1).c_str(), kCFStringEncodingUTF8); - UInt32 device_id; - AudioValueTranslation value{&uid, sizeof(CFStringRef), &device_id, - sizeof(device_id)}; - AudioObjectPropertyAddress prop{kAudioHardwarePropertyDeviceForUID, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - UInt32 size = sizeof(value); - - AudioObjectGetPropertyData( - kAudioObjectSystemObject, &prop, 0, nullptr, &size, &value); - CFRelease(uid); - - return std::make_tuple(device_id, direction); -} - -void SetAudioDeviceIsMuted(const std::string& id, bool muted) { - const UInt32 value = muted; - const auto [native_id, direction] = ParseDeviceID(id); - AudioObjectPropertyAddress prop{kAudioDevicePropertyMute, - direction == AudioDeviceDirection::INPUT - ? kAudioDevicePropertyScopeInput - : kAudioDevicePropertyScopeOutput, - 0}; - const auto result = AudioObjectSetPropertyData( - native_id, &prop, 0, NULL, sizeof(value), &value); - switch (result) { - case kAudioHardwareBadDeviceError: - case kAudioHardwareBadObjectError: - ESDLog("bad device"); - throw device_not_available_error(); - case kAudioHardwareUnsupportedOperationError: - case kAudioHardwareUnknownPropertyError: - ESDLog("bad operation"); - throw operation_not_supported_error(); - default: - ESDLog("Unhandled error {}", result); - case kAudioHardwareNoError: - break; - } -} - -}// namespace - -std::string GetDefaultAudioDeviceID( - AudioDeviceDirection direction, - AudioDeviceRole role) { - if (role != AudioDeviceRole::DEFAULT) { - return std::string(); - } - AudioDeviceID native_id = 0; - UInt32 native_id_size = sizeof(native_id); - AudioObjectPropertyAddress prop - = {direction == AudioDeviceDirection::INPUT - ? kAudioHardwarePropertyDefaultInputDevice - : kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; - - AudioObjectGetPropertyData( - kAudioObjectSystemObject, &prop, 0, NULL, &native_id_size, &native_id); - return MakeDeviceID(native_id, direction); -} - -bool IsAudioDeviceMuted(const std::string& id) { - const auto [native_id, direction] = ParseDeviceID(id); - return GetAudioObjectProperty( - native_id, - {kAudioDevicePropertyMute, - direction == AudioDeviceDirection::INPUT ? kAudioObjectPropertyScopeInput - : kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster}); -}; - -void MuteAudioDevice(const std::string& id) { - SetAudioDeviceIsMuted(id, true); -} - -void UnmuteAudioDevice(const std::string& id) { - SetAudioDeviceIsMuted(id, false); -} - -namespace { -std::string GetDataSourceName( - AudioDeviceID device_id, - AudioObjectPropertyScope scope) { - AudioObjectID data_source; - try { - data_source = GetAudioObjectProperty( - device_id, {kAudioDevicePropertyDataSource, scope, - kAudioObjectPropertyElementMaster}); - } catch (operation_not_supported_error) { - return std::string(); - } - - CFStringRef value = nullptr; - AudioValueTranslation translate{&data_source, sizeof(data_source), &value, - sizeof(value)}; - UInt32 size = sizeof(translate); - const AudioObjectPropertyAddress prop{ - kAudioDevicePropertyDataSourceNameForIDCFString, scope, - kAudioObjectPropertyElementMaster}; - AudioObjectGetPropertyData(device_id, &prop, 0, nullptr, &size, &translate); - if (!value) { - return std::string(); - } - auto ret = Utf8StringFromCFString(value); - CFRelease(value); - return ret; -} -}// namespace - -std::map GetAudioDeviceList( - AudioDeviceDirection direction) { - UInt32 size = 0; - AudioObjectPropertyAddress prop - = {kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - - AudioObjectGetPropertyDataSize( - kAudioObjectSystemObject, &prop, 0, nullptr, &size); - const auto count = size / sizeof(AudioDeviceID); - - AudioDeviceID ids[count]; - AudioObjectGetPropertyData( - kAudioObjectSystemObject, &prop, 0, nullptr, &size, ids); - - std::map out; - - // The array of devices will always contain both input and output, even if - // we set the scope above; instead filter inside the loop - const auto scope = direction == AudioDeviceDirection::INPUT - ? kAudioObjectPropertyScopeInput - : kAudioObjectPropertyScopeOutput; - for (const auto id : ids) { - const auto interface_name = GetAudioObjectProperty( - id, {kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}); - // ... and we do that filtering by finding out how many channels there - // are. No channels for a given direction? Not a valid device for that - // direction. - UInt32 size; - prop - = {kAudioDevicePropertyStreams, scope, kAudioObjectPropertyScopeGlobal}; - AudioObjectGetPropertyDataSize(id, &prop, 0, nullptr, &size); - if (size == 0) { - continue; - } - - AudioDeviceInfo info{.id = MakeDeviceID(id, direction), - .interfaceName = interface_name, - .direction = direction}; - info.state = GetAudioDeviceState(info.id); - - const auto data_source_name = GetDataSourceName(id, scope); - if (data_source_name.empty()) { - info.displayName = info.interfaceName; - } else { - info.displayName = data_source_name; - } - - out.emplace(info.id, info); - } - return out; -} - -AudioDeviceState GetAudioDeviceState(const std::string& id) { - const auto [native_id, direction] = ParseDeviceID(id); - if (!native_id) { - return AudioDeviceState::DEVICE_NOT_PRESENT; - } - const auto scope = direction == AudioDeviceDirection::INPUT - ? kAudioDevicePropertyScopeInput - : kAudioObjectPropertyScopeOutput; - - const auto transport = GetAudioObjectProperty( - native_id, {kAudioDevicePropertyTransportType, scope, - kAudioObjectPropertyElementMaster}); - - // no jack: 'Internal Speakers' - // jack: 'Headphones' - // - // Showing plugged/unplugged for these is just noise - if (transport == kAudioDeviceTransportTypeBuiltIn) { - return AudioDeviceState::CONNECTED; - } - - const AudioObjectPropertyAddress prop - = {kAudioDevicePropertyJackIsConnected, scope, - kAudioObjectPropertyElementMaster}; - const auto supports_jack = AudioObjectHasProperty(native_id, &prop); - if (!supports_jack) { - return AudioDeviceState::CONNECTED; - } - - const auto is_plugged = GetAudioObjectProperty(native_id, prop); - return is_plugged ? AudioDeviceState::CONNECTED - : AudioDeviceState::DEVICE_PRESENT_NO_CONNECTION; -} - -namespace { - -template -struct BaseCallbackHandleImpl { - typedef std::function UserCallback; - BaseCallbackHandleImpl( - UserCallback callback, - AudioDeviceID device, - AudioObjectPropertyAddress prop) - : mProp(prop), mDevice(device), mCallback(callback) { - AudioObjectAddPropertyListener(mDevice, &mProp, &OSCallback, this); - } - - virtual ~BaseCallbackHandleImpl() { - AudioObjectRemovePropertyListener(mDevice, &mProp, &OSCallback, this); - } - - private: - const AudioObjectPropertyAddress mProp; - AudioDeviceID mDevice; - UserCallback mCallback; - - static OSStatus OSCallback( - AudioDeviceID id, - UInt32 _prop_count, - const AudioObjectPropertyAddress* _props, - void* data) { - auto self = reinterpret_cast*>(data); - const auto value = GetAudioObjectProperty(id, self->mProp); - self->mCallback(value); - return 0; - } -}; - -}// namespace - -struct MuteCallbackHandleImpl : BaseCallbackHandleImpl { - MuteCallbackHandleImpl( - UserCallback cb, - AudioDeviceID device, - AudioDeviceDirection direction) - : BaseCallbackHandleImpl( - cb, - device, - {kAudioDevicePropertyMute, - direction == AudioDeviceDirection::INPUT - ? kAudioObjectPropertyScopeInput - : kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster}) { - } -}; - -MuteCallbackHandle::MuteCallbackHandle(MuteCallbackHandleImpl* impl) - : AudioDeviceCallbackHandle(impl) { -} -MuteCallbackHandle::~MuteCallbackHandle() { -} - -std::unique_ptr AddAudioDeviceMuteUnmuteCallback( - const std::string& deviceID, - std::function cb) { - const auto [id, direction] = ParseDeviceID(deviceID); - return std::make_unique( - new MuteCallbackHandleImpl(cb, id, direction)); -} - -struct DefaultChangeCallbackHandleImpl { - DefaultChangeCallbackHandleImpl( - std::function< - void(AudioDeviceDirection, AudioDeviceRole, const std::string&)> cb) - : mInputImpl( - [=](AudioDeviceID native_id) { - const auto device - = MakeDeviceID(native_id, AudioDeviceDirection::INPUT); - cb(AudioDeviceDirection::INPUT, AudioDeviceRole::DEFAULT, device); - }, - kAudioObjectSystemObject, - {kAudioHardwarePropertyDefaultInputDevice, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}), - mOutputImpl( - [=](AudioDeviceID native_id) { - const auto device - = MakeDeviceID(native_id, AudioDeviceDirection::OUTPUT); - cb(AudioDeviceDirection::OUTPUT, AudioDeviceRole::DEFAULT, device); - }, - kAudioObjectSystemObject, - {kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}) { - } - - private: - BaseCallbackHandleImpl mInputImpl; - BaseCallbackHandleImpl mOutputImpl; -}; - -DefaultChangeCallbackHandle::DefaultChangeCallbackHandle( - DefaultChangeCallbackHandleImpl* impl) - : AudioDeviceCallbackHandle(impl) { -} -DefaultChangeCallbackHandle::~DefaultChangeCallbackHandle() { -} - -std::unique_ptr -AddDefaultAudioDeviceChangeCallback( - std::function - cb) { - return std::make_unique( - new DefaultChangeCallbackHandleImpl(cb)); -} - -}// namespace FredEmmott::Audio diff --git a/Sources/AudioDevicesWindows.cpp b/Sources/AudioDevicesWindows.cpp deleted file mode 100644 index b7b8764..0000000 --- a/Sources/AudioDevicesWindows.cpp +++ /dev/null @@ -1,434 +0,0 @@ -/* Copyright (c) 2019-present, Fred Emmott - * - * This source code is licensed under the MIT-style license found in the - * LICENSE file. - */ - -// Include order matters for these; don't let the autoformatter break things -// clang-format off -#include "windows.h" -#include "endpointvolume.h" -#include "mmdeviceapi.h" -#include "PolicyConfig.h" -#include "Functiondiscoverykeys_devpkey.h" -// clang-format on - -#include "AudioDevices.h" -#include "StringEncoding.h" - -#include - -#include -#include - -using namespace FredEmmott::Encoding; - -namespace FredEmmott::Audio { - -namespace { - -EDataFlow AudioDeviceDirectionToEDataFlow(const AudioDeviceDirection dir) { - switch (dir) { - case AudioDeviceDirection::INPUT: - return eCapture; - case AudioDeviceDirection::OUTPUT: - return eRender; - } - __assume(0); -} - -ERole AudioDeviceRoleToERole(const AudioDeviceRole role) { - switch (role) { - case AudioDeviceRole::COMMUNICATION: - return eCommunications; - case AudioDeviceRole::DEFAULT: - return eConsole; - } - __assume(0); -} - -CComPtr DeviceIDToDevice(const std::string& device_id) { - static std::map> cache; - const auto cached = cache.find(device_id); - if (cached != cache.end()) { - return cached->second; - } - - CComPtr de; - de.CoCreateInstance(__uuidof(MMDeviceEnumerator)); - CComPtr device; - auto utf16 = Utf8ToUtf16(device_id); - de->GetDevice(utf16.c_str(), &device); - if (!device) { - throw device_not_available_error(); - } - cache.emplace(device_id, device); - return device; -} - -CComPtr DeviceIDToAudioEndpointVolume( - const std::string& device_id) { - static std::map> cache; - const auto cached = cache.find(device_id); - if (cached != cache.end()) { - return cached->second; - } - auto device = DeviceIDToDevice(device_id); - CComPtr volume; - device->Activate( - __uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr, (void**)&volume); - if (!volume) { - throw operation_not_supported_error(); - } - cache[device_id] = volume; - return volume; -} - -AudioDeviceState GetAudioDeviceState(CComPtr device) { - DWORD nativeState; - device->GetState(&nativeState); - - switch (nativeState) { - case DEVICE_STATE_ACTIVE: - return AudioDeviceState::CONNECTED; - case DEVICE_STATE_DISABLED: - return AudioDeviceState::DEVICE_DISABLED; - case DEVICE_STATE_NOTPRESENT: - return AudioDeviceState::DEVICE_NOT_PRESENT; - case DEVICE_STATE_UNPLUGGED: - return AudioDeviceState::DEVICE_PRESENT_NO_CONNECTION; - } - - __assume(0); -} - -}// namespace - -AudioDeviceState GetAudioDeviceState(const std::string& id) { - auto device = DeviceIDToDevice(id); - if (device == nullptr) { - return AudioDeviceState::DEVICE_NOT_PRESENT; - } - return GetAudioDeviceState(device); -} - -std::map GetAudioDeviceList( - AudioDeviceDirection direction) { - CComPtr de; - de.CoCreateInstance(__uuidof(MMDeviceEnumerator)); - - CComPtr devices; - de->EnumAudioEndpoints( - AudioDeviceDirectionToEDataFlow(direction), DEVICE_STATEMASK_ALL, &devices); - - UINT deviceCount; - devices->GetCount(&deviceCount); - std::map out; - - for (UINT i = 0; i < deviceCount; ++i) { - CComPtr device; - devices->Item(i, &device); - LPWSTR nativeID; - device->GetId(&nativeID); - const auto id = Utf16ToUtf8(nativeID); - CComPtr properties; - device->OpenPropertyStore(STGM_READ, &properties); - PROPVARIANT nativeCombinedName; - properties->GetValue(PKEY_Device_FriendlyName, &nativeCombinedName); - PROPVARIANT nativeInterfaceName; - properties->GetValue( - PKEY_DeviceInterface_FriendlyName, &nativeInterfaceName); - PROPVARIANT nativeEndpointName; - properties->GetValue(PKEY_Device_DeviceDesc, &nativeEndpointName); - - if (!nativeCombinedName.pwszVal) { - continue; - } - - out[id] = AudioDeviceInfo{ - .id = id, - .interfaceName = Utf16ToUtf8(nativeInterfaceName.pwszVal), - .endpointName = Utf16ToUtf8(nativeEndpointName.pwszVal), - .displayName = Utf16ToUtf8(nativeCombinedName.pwszVal), - .direction = direction, - .state = GetAudioDeviceState(device)}; - } - return out; -} - -std::string GetDefaultAudioDeviceID( - AudioDeviceDirection direction, - AudioDeviceRole role) { - CComPtr de; - de.CoCreateInstance(__uuidof(MMDeviceEnumerator)); - if (!de) { - ESDDebug("Failed to create MMDeviceEnumerator"); - return std::string(); - } - CComPtr device; - de->GetDefaultAudioEndpoint( - AudioDeviceDirectionToEDataFlow(direction), AudioDeviceRoleToERole(role), - &device); - if (!device) { - ESDDebug("No default audio device"); - return std::string(); - } - LPWSTR deviceID; - device->GetId(&deviceID); - if (!deviceID) { - ESDDebug("No default audio device ID"); - return std::string(); - } - return Utf16ToUtf8(deviceID); -} - -void SetDefaultAudioDeviceID( - AudioDeviceDirection direction, - AudioDeviceRole role, - const std::string& desiredID) { - if (desiredID == GetDefaultAudioDeviceID(direction, role)) { - return; - } - - CComPtr pPolicyConfig; - pPolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient)); - const auto utf16 = Utf8ToUtf16(desiredID); - pPolicyConfig->SetDefaultEndpoint( - utf16.c_str(), AudioDeviceRoleToERole(role)); -} - -bool IsAudioDeviceMuted(const std::string& deviceID) { - auto volume = DeviceIDToAudioEndpointVolume(deviceID); - if (!volume) { - return false; - } - BOOL ret; - volume->GetMute(&ret); - return ret; -} - -void MuteAudioDevice(const std::string& deviceID) { - auto volume = DeviceIDToAudioEndpointVolume(deviceID); - if (!volume) { - return; - } - volume->SetMute(true, nullptr); -} - -void UnmuteAudioDevice(const std::string& deviceID) { - auto volume = DeviceIDToAudioEndpointVolume(deviceID); - if (!volume) { - return; - } - volume->SetMute(false, nullptr); -} - -namespace { -class VolumeCOMCallback : public IAudioEndpointVolumeCallback { - public: - VolumeCOMCallback(std::function cb) : mCB(cb), mRefs(0) { - } - - ~VolumeCOMCallback() { - } - - virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ret) - override { - if (iid == IID_IUnknown || iid == __uuidof(IAudioEndpointVolumeCallback)) { - *ret = static_cast(this); - AddRef(); - return S_OK; - } - *ret = nullptr; - return E_NOINTERFACE; - } - - virtual ULONG __stdcall AddRef() override { - return InterlockedIncrement(&mRefs); - } - virtual ULONG __stdcall Release() override { - if (InterlockedDecrement(&mRefs) == 0) { - delete this; - return 0; - } - return mRefs; - } - virtual HRESULT OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) override { - mCB(pNotify->bMuted); - return S_OK; - } - - private: - std::function mCB; - long mRefs; -}; - -}// namespace - -struct MuteCallbackHandleImpl { - CComPtr impl; - CComPtr dev; - - MuteCallbackHandleImpl( - CComPtr impl, - CComPtr dev) - : impl(impl), dev(dev) { - } - - ~MuteCallbackHandleImpl() { - dev->UnregisterControlChangeNotify(impl); - } - - MuteCallbackHandleImpl(const VolumeCOMCallback& other) = delete; - MuteCallbackHandleImpl& operator=(const VolumeCOMCallback& other) = delete; -}; - -MuteCallbackHandle::MuteCallbackHandle(MuteCallbackHandleImpl* impl) - : AudioDeviceCallbackHandle(impl) { -} -MuteCallbackHandle::~MuteCallbackHandle() { -} - -std::unique_ptr AddAudioDeviceMuteUnmuteCallback( - const std::string& deviceID, - std::function cb) { - auto dev = DeviceIDToAudioEndpointVolume(deviceID); - auto impl = CComPtr(new VolumeCOMCallback(cb)); - auto ret = dev->RegisterControlChangeNotify(impl); - if (ret != S_OK) { - throw operation_not_supported_error(); - } - return std::make_unique( - new MuteCallbackHandleImpl(impl, dev)); -} - -namespace { -typedef std::function< - void(AudioDeviceDirection, AudioDeviceRole, const std::string&)> - DefaultChangeCallbackFun; -class DefaultChangeCOMCallback : public IMMNotificationClient { - public: - DefaultChangeCOMCallback(DefaultChangeCallbackFun cb) : mCB(cb), mRefs(1) { - } - - virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ret) - override { - if (iid == IID_IUnknown || iid == __uuidof(IMMNotificationClient)) { - *ret = static_cast(this); - AddRef(); - return S_OK; - } - *ret = nullptr; - return E_NOINTERFACE; - } - - virtual ULONG __stdcall AddRef() override { - return InterlockedIncrement(&mRefs); - } - virtual ULONG __stdcall Release() override { - if (InterlockedDecrement(&mRefs) == 0) { - delete this; - return 0; - } - return mRefs; - } - - virtual HRESULT OnDefaultDeviceChanged( - EDataFlow flow, - ERole winAudioDeviceRole, - LPCWSTR defaultDeviceID) override { - AudioDeviceRole role; - switch (winAudioDeviceRole) { - case ERole::eMultimedia: - return S_OK; - case ERole::eCommunications: - role = AudioDeviceRole::COMMUNICATION; - break; - case ERole::eConsole: - role = AudioDeviceRole::DEFAULT; - break; - } - const AudioDeviceDirection direction = (flow == EDataFlow::eCapture) - ? AudioDeviceDirection::INPUT - : AudioDeviceDirection::OUTPUT; - mCB(direction, role, Utf16ToUtf8(defaultDeviceID)); - - return S_OK; - }; - - virtual HRESULT OnDeviceAdded(LPCWSTR pwstrDeviceId) override { - return S_OK; - }; - - virtual HRESULT OnDeviceRemoved(LPCWSTR pwstrDeviceId) override { - return S_OK; - }; - - virtual HRESULT OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) - override { - return S_OK; - }; - - virtual HRESULT OnPropertyValueChanged( - LPCWSTR pwstrDeviceId, - const PROPERTYKEY key) override { - return S_OK; - }; - - private: - DefaultChangeCallbackFun mCB; - long mRefs; -}; - -}// namespace - -struct DefaultChangeCallbackHandleImpl { - CComPtr impl; - CComPtr enumerator; - - DefaultChangeCallbackHandleImpl( - CComPtr impl, - CComPtr enumerator) { - this->impl = impl; - this->enumerator = enumerator; - } - - ~DefaultChangeCallbackHandleImpl() { - enumerator->UnregisterEndpointNotificationCallback(this->impl); - } - - DefaultChangeCallbackHandleImpl(const DefaultChangeCallbackHandleImpl& copied) - = delete; - DefaultChangeCallbackHandleImpl& operator=( - const DefaultChangeCallbackHandleImpl& other) - = delete; -}; - -DefaultChangeCallbackHandle::DefaultChangeCallbackHandle( - DefaultChangeCallbackHandleImpl* impl) - : AudioDeviceCallbackHandle(impl) { -} - -DefaultChangeCallbackHandle::~DefaultChangeCallbackHandle() { -} - -std::unique_ptr -AddDefaultAudioDeviceChangeCallback(DefaultChangeCallbackFun cb) { - CComPtr de; - de.CoCreateInstance(__uuidof(MMDeviceEnumerator)); - if (!de) { - ESDDebug("failed to get enumerator"); - return nullptr; - } - CComPtr impl(new DefaultChangeCOMCallback(cb)); - if (de->RegisterEndpointNotificationCallback(impl) != S_OK) { - ESDDebug("failed to register default callback"); - return nullptr; - } - - ESDDebug("returning new default callback handle"); - return std::make_unique( - new DefaultChangeCallbackHandleImpl(impl, de)); -} - -}// namespace FredEmmott::Audio diff --git a/Sources/AudioMuteStreamDeckPlugin.cpp b/Sources/AudioMuteStreamDeckPlugin.cpp index 587b5fb..62b2033 100644 --- a/Sources/AudioMuteStreamDeckPlugin.cpp +++ b/Sources/AudioMuteStreamDeckPlugin.cpp @@ -14,7 +14,7 @@ #include #include -#include "AudioDevices.h" +#include #include "DefaultAudioDevices.h" #include "MuteAction.h" #include "ToggleMuteAction.h" diff --git a/Sources/BaseMuteAction.cpp b/Sources/BaseMuteAction.cpp index 1ec082b..bf4674c 100644 --- a/Sources/BaseMuteAction.cpp +++ b/Sources/BaseMuteAction.cpp @@ -10,7 +10,7 @@ #include #include -#include "AudioDevices.h" +#include #include "DefaultAudioDevices.h" using json = nlohmann::json; diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 54d6b8e..7a57f95 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -12,16 +12,13 @@ set( if(WIN32) list( APPEND SOURCES - StringEncoding.cpp PlayWavFileWindows.cpp - AudioDevicesWindows.cpp ) endif() if(APPLE) list( APPEND SOURCES PlayWavFileMacOS.cpp - AudioDevicesMacOS.cpp ) endif() @@ -30,16 +27,12 @@ add_executable( ${SOURCES} ) -target_link_libraries(sdmicmute StreamDeckSDK) +target_link_libraries(sdmicmute StreamDeckSDK AudioDeviceLib) if(APPLE) find_library(AUDIOTOOLBOX_FRAMEWORK AudioToolbox) - find_library(COREAUDIO_FRAMEWORK CoreAudio) - find_library(COREFOUNDATION_FRAMEWORK CoreFoundation) target_link_libraries( sdmicmute ${AUDIOTOOLBOX_FRAMEWORK} - ${COREAUDIO_FRAMEWORK} - ${COREFOUNDATION_FRAMEWORK} ) elseif(WIN32) target_link_libraries(sdmicmute Winmm) diff --git a/Sources/DefaultAudioDevices.cpp b/Sources/DefaultAudioDevices.cpp index 1fbfbb4..1ac2883 100644 --- a/Sources/DefaultAudioDevices.cpp +++ b/Sources/DefaultAudioDevices.cpp @@ -6,7 +6,7 @@ #include "DefaultAudioDevices.h" -#include "AudioDevices.h" +#include const std::string DefaultAudioDevices::DEFAULT_INPUT_ID( "com.fredemmott.sdmute.deviceIds.defaultInput"); diff --git a/Sources/DefaultAudioDevices.h b/Sources/DefaultAudioDevices.h index 2bf39d8..3e979f7 100644 --- a/Sources/DefaultAudioDevices.h +++ b/Sources/DefaultAudioDevices.h @@ -6,7 +6,7 @@ #pragma once -#include "AudioDevices.h" +#include #include using namespace FredEmmott::Audio; diff --git a/Sources/MuteAction.cpp b/Sources/MuteAction.cpp index e698f6e..7f10fb2 100644 --- a/Sources/MuteAction.cpp +++ b/Sources/MuteAction.cpp @@ -8,7 +8,7 @@ #include #include -#include "AudioDevices.h" +#include #include "PlayWavFile.h" const std::string MuteAction::ACTION_ID("com.fredemmott.micmutetoggle.mute"); diff --git a/Sources/StringEncoding.cpp b/Sources/StringEncoding.cpp deleted file mode 100644 index af2fecc..0000000 --- a/Sources/StringEncoding.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* Copyright (c) 2020-present, Fred Emmott - * - * This source code is licensed under the MIT-style license found in the - * LICENSE file. - */ - -#include "StringEncoding.h" - -#include "Windows.h" - -namespace FredEmmott::Encoding { - -std::string Utf16ToUtf8(const std::wstring& utf16) { - if (utf16.empty()) { - return std::string(); - } - int utf8_len - = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), utf16.size(), 0, 0, 0, 0); - std::string buf(utf8_len, 0); - WideCharToMultiByte(CP_UTF8, 0, utf16.data(), -1, buf.data(), utf8_len, 0, 0); - return buf; -} - -std::wstring Utf8ToUtf16(const std::string& utf8) { - if (utf8.empty()) { - return std::wstring(); - } - int wchar_len - = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), utf8.size(), 0, 0); - std::wstring buf(wchar_len, 0); - MultiByteToWideChar( - CP_UTF8, 0, utf8.data(), utf8.size(), buf.data(), wchar_len); - return buf; -} -}// namespace FredEmmott::Encoding diff --git a/Sources/StringEncoding.h b/Sources/StringEncoding.h deleted file mode 100644 index 15fd193..0000000 --- a/Sources/StringEncoding.h +++ /dev/null @@ -1,14 +0,0 @@ -/* Copyright (c) 2020-present, Fred Emmott - * - * This source code is licensed under the MIT-style license found in the - * LICENSE file. - */ - -#pragma once - -#include - -namespace FredEmmott::Encoding { - std::string Utf16ToUtf8(const std::wstring& utf16); - std::wstring Utf8ToUtf16(const std::string& utf8); -} diff --git a/Sources/ToggleMuteAction.cpp b/Sources/ToggleMuteAction.cpp index dd95aef..6b4f22d 100644 --- a/Sources/ToggleMuteAction.cpp +++ b/Sources/ToggleMuteAction.cpp @@ -9,7 +9,7 @@ #include #include -#include "AudioDevices.h" +#include #include "MuteAction.h" #include "UnmuteAction.h" diff --git a/Sources/UnmuteAction.cpp b/Sources/UnmuteAction.cpp index 24a6f96..a626a8b 100644 --- a/Sources/UnmuteAction.cpp +++ b/Sources/UnmuteAction.cpp @@ -9,7 +9,7 @@ #include #include -#include "AudioDevices.h" +#include #include "PlayWavFile.h" const std::string UnmuteAction::ACTION_ID(