Skip to content

Commit

Permalink
WASAPI: Implement node addition/removal callback(s)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidebeatrici committed Nov 8, 2024
1 parent 8fd1818 commit dd421b1
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 79 deletions.
4 changes: 4 additions & 0 deletions src/backends/WASAPI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ target_include_directories(be_wasapi

target_sources(be_wasapi
PRIVATE
"Device.cpp"
"Device.hpp"
"EventManager.cpp"
"EventManager.hpp"
"WASAPI.cpp"
"WASAPI.hpp"
)
Expand Down
117 changes: 117 additions & 0 deletions src/backends/WASAPI/Device.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2024 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#include "Device.hpp"

#include "Node.h"

#include <cstdlib>
#include <cstring>

#include <Windows.h>

#include <audioclient.h>
#include <functiondiscoverykeys.h>
#include <mmdeviceapi.h>

using Direction = CrossAudio_Direction;

namespace wasapi {
static Direction getDirection(IMMDevice &device) {
IMMEndpoint *endpoint;
if (device.QueryInterface(__uuidof(IMMEndpoint), reinterpret_cast< void ** >(&endpoint)) != S_OK) {
return CROSSAUDIO_DIR_NONE;
}

EDataFlow dataflow;
if (endpoint->GetDataFlow(&dataflow) != S_OK) {
dataflow = EDataFlow_enum_count;
}

endpoint->Release();

switch (dataflow) {
case eRender:
return CROSSAUDIO_DIR_OUT;
case eCapture:
return CROSSAUDIO_DIR_IN;
case eAll:
return CROSSAUDIO_DIR_BOTH;
case EDataFlow_enum_count:
break;
}

return CROSSAUDIO_DIR_NONE;
}

static char *getID(IMMDevice &device) {
wchar_t *deviceID;
if (device.GetId(&deviceID) != S_OK) {
return nullptr;
}

char *id = utf16To8(deviceID);

CoTaskMemFree(deviceID);

return id;
}

static char *getName(IMMDevice &device) {
IPropertyStore *store;
if (device.OpenPropertyStore(STGM_READ, &store) != S_OK) {
return nullptr;
}

char *name = nullptr;

PROPVARIANT varName{};
if (store->GetValue(PKEY_Device_FriendlyName, &varName) == S_OK) {
name = utf16To8(varName.pwszVal);
PropVariantClear(&varName);
}

store->Release();

return name;
}

bool populateNode(Node &node, IMMDevice &device, const wchar_t *id) {
if (!(node.id = id ? utf16To8(id) : getID(device))) {
return false;
}

node.name = getName(device);
node.direction = getDirection(device);

return true;
}

char *utf16To8(const wchar_t *utf16) {
const auto utf16Size = static_cast< int >(wcslen(utf16) + 1);

const auto utf8Size = WideCharToMultiByte(CP_UTF8, 0, utf16, utf16Size, nullptr, 0, nullptr, nullptr);
auto utf8 = static_cast< char *>(malloc(utf8Size));
if (WideCharToMultiByte(CP_UTF8, 0, utf16, utf16Size, utf8, utf8Size, nullptr, nullptr) <= 0) {
free(utf8);
return nullptr;
}

return utf8;
}

wchar_t *utf8To16(const char *utf8) {
const auto utf8Size = static_cast< int >(strlen(utf8) + 1);

const int utf16Size = sizeof(wchar_t) * MultiByteToWideChar(CP_UTF8, 0, utf8, utf8Size, nullptr, 0);
auto utf16 = static_cast< wchar_t *>(malloc(utf16Size));
if (MultiByteToWideChar(CP_UTF8, 0, utf8, utf8Size, utf16, utf16Size / sizeof(wchar_t)) <= 0) {
free(utf16);
return nullptr;
}

return utf16;
}
} // namespace wasapi
20 changes: 20 additions & 0 deletions src/backends/WASAPI/Device.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2024 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#ifndef CROSSAUDIO_SRC_BACKENDS_WASAPI_DEVICE_HPP
#define CROSSAUDIO_SRC_BACKENDS_WASAPI_DEVICE_HPP

using Node = struct CrossAudio_Node;

struct IMMDevice;

namespace wasapi {
bool populateNode(Node &node, IMMDevice &device, const wchar_t *id);

char *utf16To8(const wchar_t *utf16);
wchar_t *utf8To16(const char *utf8);
} // namespace wasapi

#endif
111 changes: 111 additions & 0 deletions src/backends/WASAPI/EventManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2024 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#include "EventManager.hpp"

#include "Device.hpp"

#include "Node.h"

#include <cstdio>

using namespace wasapi;

EventManager::EventManager(IMMDeviceEnumerator *enumerator, const Feedback &feedback)
: m_feedback(feedback), m_ref(0), m_enumerator(enumerator) {
m_enumerator->AddRef();
m_enumerator->RegisterEndpointNotificationCallback(this);
}

EventManager::~EventManager() {
m_enumerator->UnregisterEndpointNotificationCallback(this);
m_enumerator->Release();
}

ULONG STDMETHODCALLTYPE EventManager::AddRef() {
return InterlockedIncrement(&m_ref);
}

ULONG STDMETHODCALLTYPE EventManager::Release() {
return InterlockedDecrement(&m_ref);
}

HRESULT STDMETHODCALLTYPE EventManager::QueryInterface(const IID &iid, void **iface) {
if (iid == IID_IUnknown) {
*iface = static_cast< IUnknown * >(this);
AddRef();
} else if (iid == __uuidof(IMMNotificationClient)) {
*iface = static_cast< IMMNotificationClient * >(this);
AddRef();
} else {
*iface = nullptr;
return E_NOINTERFACE;
}

return S_OK;
}

HRESULT STDMETHODCALLTYPE EventManager::OnDeviceAdded(const wchar_t *id) {
wprintf(L"OnDeviceAdded: %s\n", id);

IMMDevice *device;
if (m_enumerator->GetDevice(id, &device) != S_OK) {
return S_OK;
}

Node *node = nodeNew();
if (populateNode(*node, *device, id)) {
m_feedback.nodeAdded(node);
}

device->Release();

return S_OK;
}

HRESULT STDMETHODCALLTYPE EventManager::OnDeviceRemoved(const wchar_t *id) {
wprintf(L"OnDeviceRemoved: %s\n", id);

IMMDevice *device;
if (m_enumerator->GetDevice(id, &device) != S_OK) {
return S_OK;
}

Node *node = nodeNew();
if (populateNode(*node, *device, id)) {
m_feedback.nodeRemoved(node);
}

device->Release();

return S_OK;
}

HRESULT STDMETHODCALLTYPE EventManager::OnDeviceStateChanged(const wchar_t *id, DWORD state) {
wprintf(L"OnDeviceStateChanged: %s\n", id);

switch (state) {
case DEVICE_STATE_ACTIVE:
return OnDeviceAdded(id);
case DEVICE_STATE_DISABLED:
case DEVICE_STATE_NOTPRESENT:
case DEVICE_STATE_UNPLUGGED:
return OnDeviceRemoved(id);
}

return S_OK;
}

HRESULT STDMETHODCALLTYPE EventManager::OnDefaultDeviceChanged(EDataFlow, ERole, const wchar_t *id) {
wprintf(L"OnDefaultDeviceChanged: %s\n", id);

return S_OK;
}

HRESULT STDMETHODCALLTYPE EventManager::OnPropertyValueChanged(const wchar_t *id, const PROPERTYKEY) {
wprintf(L"OnPropertyValueChanged: %s\n", id);

return S_OK;
}
46 changes: 46 additions & 0 deletions src/backends/WASAPI/EventManager.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2024 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#ifndef CROSSAUDIO_SRC_BACKENDS_WASAPI_EVENTMANAGER_HPP
#define CROSSAUDIO_SRC_BACKENDS_WASAPI_EVENTMANAGER_HPP

#include <functional>

#include <mmdeviceapi.h>

using Node = struct CrossAudio_Node;

namespace wasapi {
class EventManager : public IMMNotificationClient {
public:
struct Feedback {
std::function< void(Node *node) > nodeAdded;
std::function< void(Node *node) > nodeRemoved;
};

EventManager(IMMDeviceEnumerator *enumerator, const Feedback &feedback);
virtual ~EventManager();

private:
EventManager(const EventManager &) = delete;
EventManager &operator=(const EventManager &) = delete;

ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;

HRESULT STDMETHODCALLTYPE QueryInterface(const IID &iid, void **iface) override;
HRESULT STDMETHODCALLTYPE OnDeviceAdded(const wchar_t *id) override;
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(const wchar_t *id) override;
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(const wchar_t *id, DWORD state) override;
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, const wchar_t *id) override;
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(const wchar_t *id, const PROPERTYKEY key) override;

Feedback m_feedback;
LONG m_ref;
IMMDeviceEnumerator *m_enumerator;
};
} // namespace wasapi

#endif
Loading

0 comments on commit dd421b1

Please sign in to comment.