如何在Windows中检测默认音频输出设备的变化

d7v8vwbk  于 2023-01-02  发布在  Windows
关注(0)|答案(1)|浏览(277)

我正在用C++开发一个音频录制程序(如果有帮助的话,我使用OpenAL和ImGui以及OpenGL),我想知道我是否可以检测到我的默认音频输出设备是否发生了变化,而不需要运行一个循环来阻止我的程序。有没有一种方法可以让我像回调一样检测到它?
我试着用

alcGetString(device, ALC_DEFAULT_DEVICE_SPECIFIER);

函数来获取默认设备的名称,并将其与我在另一个线程上的循环中使用的最后一个设备进行比较。它完成了这项工作,但它让我损失了很多性能。

3xiyfsfu

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),以处理我的应用的音频输出设备更改。您还可以向函数(如OnDeviceAddedOnDeviceRemoved)添加自己的逻辑,以处理其他类型的事件,但是因为我不需要它们,所以我只从这些函数返回s_Ok。你也应该知道这些函数是纯函数-虚函数,所以即使你不想使用它们,你也需要覆盖它们。我使用IMMDeviceEnumerator,这样我就可以注册我继承的NotificationClient类来接收音频设备消息。但是如果COM库没有初始化,那么你需要调用CoInitialize函数来初始化它。我创建了一个带循环的线程,使用PeekMessage获取消息,并使用WaitMessage函数挂起线程,直到它接收到另一条消息。这解决了我使用忙循环不断检查消息时的性能问题。为了安全地关闭此线程,我使用PostThreadMessage函数向线程发送WM_QUIT消息,并使用WaitForSingleObject等待它关闭。
我将所有这些打包到AudioDeviceNotificationListener类中,这样我就可以调用Start函数开始监听消息,调用Close函数退出线程并停止监听。
(Edit:我也找到了不创建线程的方法。代码差不多,我只是去掉了AudioDeviceNotificationListener类。代码如下所示)

// 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;
    };

你可以使用这些代码之一为您自己的喜好,他们都为我工作。

相关问题