PCのマイク・スピーカー(オーディオデバイス)の音量変更・ミュート状態変更時のイベントを取る

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

マイクやスピーカーの音量変更、ミュート状態が変更されたことを知りたい。

やり方

IAudioEndpointVolumeCallbackインターフェースクラスを実装したクラスをRegisterControlChangeNotify()に渡してやる。

下記を参考にして実装。

https://learn.microsoft.com/ja-jp/windows/win32/api/endpointvolume/nf-endpointvolume-iaudioendpointvolume-registercontrolchangenotify

サンプルコード

下記にコードを置いた。

https://github.com/tera1707/AudioDevice

IAudioEndpointVolumeCallbackインターフェースクラスを実装したクラス

#include <windows.h>
#include <mmdeviceapi.h>
#include <functiondiscoverykeys.h>
#include <endpointvolume.h>
#include <string>

#include "CVolumeNotification.h"

CVolumeNotification::CVolumeNotification(std::wstring targetEndpointName) : _cRef(1) 
{
    this->targetEndpointName = targetEndpointName;
}

CVolumeNotification::~CVolumeNotification() {}

ULONG STDMETHODCALLTYPE CVolumeNotification::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

ULONG STDMETHODCALLTYPE CVolumeNotification::Release()
{
    ULONG ulRef = InterlockedDecrement(&_cRef);
    if (0 == ulRef)
    {
        delete this;
    }
    return ulRef;
}

HRESULT STDMETHODCALLTYPE CVolumeNotification::QueryInterface(REFIID riid, VOID** ppvInterface)
{
    if (IID_IUnknown == riid)
    {
        AddRef();
        *ppvInterface = (IUnknown*)this;
    }
    else if (__uuidof(IAudioEndpointVolumeCallback) == riid)
    {
        AddRef();
        *ppvInterface = (IAudioEndpointVolumeCallback*)this;
    }
    else
    {
        *ppvInterface = NULL;
        return E_NOINTERFACE;
    }

    return S_OK;
}

HRESULT STDMETHODCALLTYPE CVolumeNotification::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify)
{
    if (!pNotify)
    {
        return E_INVALIDARG;
    }

    auto mute = pNotify->bMuted;
    auto volume = pNotify->fMasterVolume;

    OutputDebugString((targetEndpointName + L" mute:" + std::to_wstring(mute) + L" volume:" + std::to_wstring(volume) + L"\r\n").c_str());

    return S_OK;
}

IAudioEndpointVolumeCallbackインターフェースクラスを実装したクラスのヘッダー

#pragma once
#include <windows.h>
#include <mmdeviceapi.h>
#include <functiondiscoverykeys.h>
#include <endpointvolume.h>
#include <string>

class CVolumeNotification : public IAudioEndpointVolumeCallback
{
    ULONG _cRef;
    std::wstring targetEndpointName;
public:
    CVolumeNotification(std::wstring targetEndpointName);
    ~CVolumeNotification();

    ULONG STDMETHODCALLTYPE AddRef();
    ULONG STDMETHODCALLTYPE Release();
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface);
    HRESULT STDMETHODCALLTYPE OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify);
};

メイン

#include <windows.h>
#include <mmdeviceapi.h>
#include <functiondiscoverykeys.h>
#include <endpointvolume.h>
#include <string>

#include "CMMNotificationClient.h"
#include "CVolumeNotification.h"

int main()
{
    HRESULT hr;
    IMMDeviceEnumerator* pEnum = NULL;
    IMMDeviceCollection* pCollection = NULL;
    UINT deviceCount = 0;

    IMMDevice* pEndpoint[8] = { NULL };
    IPropertyStore* pProperties[8] = { NULL };

    IAudioEndpointVolume* pAudioEndVol[8] = { NULL };
    CVolumeNotification* vn[8] = { NULL };

    // COMの初期化(COMのお作法)
    hr = CoInitializeEx(0, COINIT_MULTITHREADED);

    // COMからMMDeviceEnumeratorを取ってくる
    hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, IID_PPV_ARGS(&pEnum));

    auto nc = new CMMNotificationClient(pEnum);
    pEnum->RegisterEndpointNotificationCallback(nc);

    // オーディオエンドポイントの列挙を実行
    hr = pEnum->EnumAudioEndpoints(EDataFlow::eCapture, DEVICE_STATE_ACTIVE, &pCollection);

    // とれた数を数える
    hr = pCollection->GetCount(&deviceCount);

    for (int i = 0; i < deviceCount; i++)
    {
        // デバイスの情報を取る
        pCollection->Item(i, &pEndpoint[i]);

        // デバイスのプロパティを取る
        pEndpoint[i]->OpenPropertyStore(STGM_READ, &pProperties[i]);

        // 取ったプロパティから、値を指定して取得する
        PROPVARIANT vName;
        PropVariantInit(&vName);
        pProperties[i]->GetValue(PKEY_Device_FriendlyName, &vName);

        // エンドポイントの情報を取る
        hr = pEndpoint[i]->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void**)&pAudioEndVol[i]);

        // 音量・ミュート状態の変更検知
        vn[i] = new CVolumeNotification(vName.pwszVal);
        pAudioEndVol[i]->RegisterControlChangeNotify(vn[i]);  // ★★ココが重要!!!

        //// エンドポイントに指示を出す(「IAudioEndpointVolume」のVolumeは、マイクの怨霊という意味の「ボリューム」とは別の意味っぽい)
        float getVolume = 0.0f;
        pAudioEndVol[i]->GetMasterVolumeLevelScalar(&getVolume);              // 音量を取得
        float setVolume = 0.25f;
        pAudioEndVol[i]->SetMasterVolumeLevelScalar(setVolume, &GUID_NULL);   // 音量を設定

        std::wstring outString = std::to_wstring(i) + L":" + vName.pwszVal + std::to_wstring(getVolume) + L"\r\n";
        OutputDebugString(outString.c_str());

        // 後処理系
        if (pProperties)
            pProperties[i]->Release();
    }

    while (1)
    {
        Sleep(100000);
    }

    // 後処理系
    for (size_t i = 0; i < sizeof(pAudioEndVol) / sizeof(pAudioEndVol[0]); i++)
    {
        if (pAudioEndVol[i] != NULL)
            pAudioEndVol[i]->Release();
        if (pEndpoint[i] != NULL)
            pEndpoint[i]->Release();
    }
    if (pCollection)
        pCollection->Release();
    if (pEnum)
        pEnum->Release();

    // COMの後処理(COMのお作法)
    CoUninitialize();
}

参考

IAudioEndpointVolume::RegisterControlChangeNotify メソッド (endpointvolume.h)

https://learn.microsoft.com/ja-jp/windows/win32/api/endpointvolume/nf-endpointvolume-iaudioendpointvolume-registercontrolchangenotify