我正在用C++开发一个音频录制程序(如果有帮助的话,我使用OpenAL和ImGui以及OpenGL),我想知道我是否可以检测到我的默认音频输出设备是否发生了变化,而不需要运行一个循环来阻止我的程序。有没有一种方法可以让我像回调一样检测到它?我试着用
alcGetString(device, ALC_DEFAULT_DEVICE_SPECIFIER);
函数来获取默认设备的名称,并将其与我在另一个线程上的循环中使用的最后一个设备进行比较。它完成了这项工作,但它让我损失了很多性能。
3xiyfsfu1#
感谢@ Sanders Pauli找到了一个解决方案。它不是我一直在寻找的,但我认为它仍然是一个很好的解决方案。下面是代码:
#include <Windows.h> #include <mmdeviceapi.h> #include <endpointvolume.h> // The notification client class class NotificationClient : public IMMNotificationClient { public: // IUnknown methods HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { if (riid == IID_IUnknown || riid == __uuidof(IMMNotificationClient)) { *ppvObject = this; AddRef(); return S_OK; } *ppvObject = NULL; return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&m_cRef); } ULONG STDMETHODCALLTYPE Release() { ULONG ulRef = InterlockedDecrement(&m_cRef); if (ulRef == 0) delete this; return ulRef; } // IMMNotificationClient methods HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { // The default audio output device has changed // Handle the device change event // ... return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) { return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) { return S_OK; } HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { return S_OK; } HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { return S_OK; } // Constructor and destructor NotificationClient() : m_cRef(1) {} ~NotificationClient() {} private: long m_cRef; }; class AudioDeviceNotificationListener { public: AudioDeviceNotificationListener() = default; ~AudioDeviceNotificationListener() { Close(); } bool Start() { if (!bDidStart) { // Initialize the COM library for the current thread HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { return false; } // Create the device enumerator hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator); if (FAILED(hr)) { CoUninitialize(); return false; } // Create the notification client object pNotificationClient = new NotificationClient(); // Register the notification client hr = pEnumerator->RegisterEndpointNotificationCallback(pNotificationClient); if (FAILED(hr)) { pEnumerator->Release(); pNotificationClient->Release(); pNotificationClient = nullptr; CoUninitialize(); return false; } // Create the notification thread hNotificationThread = CreateThread(NULL, 0, &AudioDeviceNotificationListener::NotificationThreadProc, pNotificationClient, 0, NULL); if (hNotificationThread == NULL) { pEnumerator->UnregisterEndpointNotificationCallback(pNotificationClient); pEnumerator->Release(); pNotificationClient->Release(); pNotificationClient = nullptr; CoUninitialize(); return false; } bDidStart = true; return true; } return false; } void Close() { if (bDidStart) { // Clean up CloseThread(); pEnumerator->UnregisterEndpointNotificationCallback(pNotificationClient); pEnumerator->Release(); pNotificationClient->Release(); pNotificationClient = nullptr; CoUninitialize(); bDidStart = false; } } private: void CloseThread() { PostThreadMessage(GetThreadId(hNotificationThread), WM_QUIT, NULL, NULL); WaitForSingleObject(hNotificationThread, INFINITE); } // Thread Function static DWORD WINAPI NotificationThreadProc(LPVOID lpParameter) { // Run the message loop MSG msg; while (true) { // Check for a message if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // A message was received. Process it. TranslateMessage(&msg); // If WM_QUIT message is received quit the thread if (msg.message == WM_QUIT) { break; } DispatchMessage(&msg); } else { // No message was received. Suspend the thread until a message is received. WaitMessage(); } } return 0; } private: bool bDidStart = false; NotificationClient* pNotificationClient = nullptr; IMMDeviceEnumerator* pEnumerator = NULL; HANDLE hNotificationThread = NULL; };
让我来解释代码:NotificationClient类继承了IMMNotificationClient,因此我可以覆盖其函数(如OnDefaultDeviceChanged),以处理我的应用的音频输出设备更改。您还可以向函数(如OnDeviceAdded或OnDeviceRemoved)添加自己的逻辑,以处理其他类型的事件,但是因为我不需要它们,所以我只从这些函数返回s_Ok。你也应该知道这些函数是纯函数-虚函数,所以即使你不想使用它们,你也需要覆盖它们。我使用IMMDeviceEnumerator,这样我就可以注册我继承的NotificationClient类来接收音频设备消息。但是如果COM库没有初始化,那么你需要调用CoInitialize函数来初始化它。我创建了一个带循环的线程,使用PeekMessage获取消息,并使用WaitMessage函数挂起线程,直到它接收到另一条消息。这解决了我使用忙循环不断检查消息时的性能问题。为了安全地关闭此线程,我使用PostThreadMessage函数向线程发送WM_QUIT消息,并使用WaitForSingleObject等待它关闭。我将所有这些打包到AudioDeviceNotificationListener类中,这样我就可以调用Start函数开始监听消息,调用Close函数退出线程并停止监听。(Edit:我也找到了不创建线程的方法。代码差不多,我只是去掉了AudioDeviceNotificationListener类。代码如下所示)
NotificationClient
IMMNotificationClient
OnDefaultDeviceChanged
OnDeviceAdded
OnDeviceRemoved
s_Ok
IMMDeviceEnumerator
CoInitialize
PeekMessage
WaitMessage
PostThreadMessage
WM_QUIT
WaitForSingleObject
AudioDeviceNotificationListener
Start
Close
// The notification client class class NotificationClient : public IMMNotificationClient { public: NotificationClient() { Start(); } ~NotificationClient() { Close(); } bool Start() { // Initialize the COM library for the current thread HRESULT ihr = CoInitialize(NULL); if (SUCCEEDED(ihr)) { // Create the device enumerator IMMDeviceEnumerator* pEnumerator; HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator); if (SUCCEEDED(hr)) { // Register for device change notifications hr = pEnumerator->RegisterEndpointNotificationCallback(this); m_pEnumerator = pEnumerator; return true; } CoUninitialize(); } return false; } void Close() { // Unregister the device enumerator if (m_pEnumerator) { m_pEnumerator->UnregisterEndpointNotificationCallback(this); m_pEnumerator->Release(); } // Uninitialize the COM library for the current thread CoUninitialize(); } // IUnknown methods STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) { if (riid == IID_IUnknown || riid == __uuidof(IMMNotificationClient)) { *ppvObject = static_cast<IMMNotificationClient*>(this); AddRef(); return S_OK; } return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&m_cRef); } ULONG STDMETHODCALLTYPE Release() { ULONG ulRef = InterlockedDecrement(&m_cRef); if (0 == ulRef) { delete this; } return ulRef; } // IMMNotificationClient methods STDMETHOD(OnDefaultDeviceChanged)(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { // Default audio device has been changed. return S_OK; } STDMETHOD(OnDeviceAdded)(LPCWSTR pwstrDeviceId) { // A new audio device has been added. return S_OK; } STDMETHOD(OnDeviceRemoved)(LPCWSTR pwstrDeviceId) { // An audio device has been removed. return S_OK; } STDMETHOD(OnDeviceStateChanged)(LPCWSTR pwstrDeviceId, DWORD dwNewState) { // The state of an audio device has changed. return S_OK; } STDMETHOD(OnPropertyValueChanged)(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { // A property value of an audio device has changed. return S_OK; } private: LONG m_cRef; IMMDeviceEnumerator* m_pEnumerator; };
你可以使用这些代码之一为您自己的喜好,他们都为我工作。
1条答案
按热度按时间3xiyfsfu1#
感谢@ Sanders Pauli找到了一个解决方案。它不是我一直在寻找的,但我认为它仍然是一个很好的解决方案。下面是代码:
让我来解释代码:
NotificationClient
类继承了IMMNotificationClient
,因此我可以覆盖其函数(如OnDefaultDeviceChanged
),以处理我的应用的音频输出设备更改。您还可以向函数(如OnDeviceAdded
或OnDeviceRemoved
)添加自己的逻辑,以处理其他类型的事件,但是因为我不需要它们,所以我只从这些函数返回s_Ok
。你也应该知道这些函数是纯函数-虚函数,所以即使你不想使用它们,你也需要覆盖它们。我使用IMMDeviceEnumerator
,这样我就可以注册我继承的NotificationClient
类来接收音频设备消息。但是如果COM库没有初始化,那么你需要调用CoInitialize
函数来初始化它。我创建了一个带循环的线程,使用PeekMessage
获取消息,并使用WaitMessage
函数挂起线程,直到它接收到另一条消息。这解决了我使用忙循环不断检查消息时的性能问题。为了安全地关闭此线程,我使用PostThreadMessage
函数向线程发送WM_QUIT
消息,并使用WaitForSingleObject
等待它关闭。我将所有这些打包到
AudioDeviceNotificationListener
类中,这样我就可以调用Start
函数开始监听消息,调用Close
函数退出线程并停止监听。(Edit:我也找到了不创建线程的方法。代码差不多,我只是去掉了
AudioDeviceNotificationListener
类。代码如下所示)你可以使用这些代码之一为您自己的喜好,他们都为我工作。