もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
前回の記事(下記)で、マイクやスピーカのデバイス(エンドポイント)の名前を取得したり、現在の音量を取得、設定したりした。
https://tera1707.com/entry/2023/10/10/224852
今回は、それらのデバイスを抜きさししたときに、それを知りたい。(抜きさし時のイベントを取りたい。)
やったこと
IMMNotificationClient
インターフェースクラスを継承したクラスを作って、それをRegisterEndpointNotificationCallback()
する。
下記MSDocsページを参考にした。
https://learn.microsoft.com/ja-jp/windows/win32/coreaudio/device-events
サンプルコード
ここにコードを置いた。
https://github.com/tera1707/AudioDevice
IMMNotificationClientを実装したクラス
#include "CMMNotificationClient.h" std::string WStringToString(std::wstring oWString); #define SAFE_RELEASE(punk) \ if ((punk) != NULL) \ { (punk)->Release(); (punk) = NULL; } CMMNotificationClient::CMMNotificationClient(IMMDeviceEnumerator* pEnum) : _cRef(1) { _pEnumerator = pEnum; } CMMNotificationClient::~CMMNotificationClient() { //SAFE_RELEASE(_pEnumerator) } //////////////////////////////////////////////////////////////// // IUnknown methods -- AddRef, Release, and QueryInterface //////////////////////////////////////////////////////////////// ULONG STDMETHODCALLTYPE CMMNotificationClient::AddRef() { return InterlockedIncrement(&_cRef); } ULONG STDMETHODCALLTYPE CMMNotificationClient::Release() { ULONG ulRef = InterlockedDecrement(&_cRef); if (0 == ulRef) { delete this; } return ulRef; } HRESULT STDMETHODCALLTYPE CMMNotificationClient::QueryInterface(REFIID riid, VOID** ppvInterface) { if (IID_IUnknown == riid) { AddRef(); *ppvInterface = (IUnknown*)this; } else if (__uuidof(IMMNotificationClient) == riid) { AddRef(); *ppvInterface = (IMMNotificationClient*)this; } else { *ppvInterface = NULL; return E_NOINTERFACE; } return S_OK; } //////////////////////////////////////////////////////////////// // Callback methods for device-event notifications. //////////////////////////////////////////////////////////////// HRESULT STDMETHODCALLTYPE CMMNotificationClient::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { if (pwstrDeviceId != NULL) { auto devName = GetDevName(pwstrDeviceId); printf(devName.c_str()); } std::string pszFlow = LPSTR("?????"); std::string pszRole = LPSTR("?????"); switch (flow) { case eRender: pszFlow = "eRender"; break; case eCapture: pszFlow = "eCapture"; break; } switch (role) { case eConsole: pszRole = "eConsole"; break; case eMultimedia: pszRole = "eMultimedia"; break; case eCommunications: pszRole = "eCommunications"; break; } printf(" -->New default device: flow = %s, role = %s\n", pszFlow.c_str(), pszRole.c_str()); return S_OK; } HRESULT STDMETHODCALLTYPE CMMNotificationClient::OnDeviceAdded(LPCWSTR pwstrDeviceId) { auto devName = GetDevName(pwstrDeviceId); printf(devName.c_str()); printf(" -->Added device\n"); return S_OK; }; HRESULT STDMETHODCALLTYPE CMMNotificationClient::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { auto devName = GetDevName(pwstrDeviceId); printf(devName.c_str()); printf(" -->Removed device\n"); return S_OK; } HRESULT STDMETHODCALLTYPE CMMNotificationClient::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { auto devName = GetDevName(pwstrDeviceId); printf(devName.c_str()); std::string pszState = "?????"; switch (dwNewState) { case DEVICE_STATE_ACTIVE: pszState = "ACTIVE"; break; case DEVICE_STATE_DISABLED: pszState = "DISABLED"; break; case DEVICE_STATE_NOTPRESENT: pszState = "NOTPRESENT"; break; case DEVICE_STATE_UNPLUGGED: pszState = "UNPLUGGED"; break; } printf(" -->New device state is DEVICE_STATE_%s (0x%8.8x)\n", pszState.c_str(), dwNewState); return S_OK; } HRESULT STDMETHODCALLTYPE CMMNotificationClient::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { auto devName = GetDevName(pwstrDeviceId); printf(devName.c_str()); printf(" -->Changed device property " "{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}#%d\n", key.fmtid.Data1, key.fmtid.Data2, key.fmtid.Data3, key.fmtid.Data4[0], key.fmtid.Data4[1], key.fmtid.Data4[2], key.fmtid.Data4[3], key.fmtid.Data4[4], key.fmtid.Data4[5], key.fmtid.Data4[6], key.fmtid.Data4[7], key.pid); return S_OK; } std::string CMMNotificationClient::GetDevName(LPCWSTR pwstrDeviceId) { IMMDevice* device = NULL; _pEnumerator->GetDevice(pwstrDeviceId, &device); IPropertyStore* pProperties; device->OpenPropertyStore(STGM_READ, &pProperties); PROPVARIANT vName; PropVariantInit(&vName); pProperties->GetValue(PKEY_Device_FriendlyName, &vName); return WStringToString(std::wstring(vName.pwszVal == NULL ? L"" : vName.pwszVal)); } // ------------------------------------------------------------------------ std::string WStringToString(std::wstring oWString) { // wstring → SJIS int iBufferSize = WideCharToMultiByte(CP_OEMCP, 0, oWString.c_str() , -1, (char*)NULL, 0, NULL, NULL); // バッファの取得 CHAR* cpMultiByte = new CHAR[iBufferSize]; // wstring → SJIS WideCharToMultiByte(CP_OEMCP, 0, oWString.c_str(), -1, cpMultiByte , iBufferSize, NULL, NULL); // stringの生成 std::string oRet(cpMultiByte, cpMultiByte + iBufferSize - 1); // バッファの破棄 delete[] cpMultiByte; // 変換結果を返す return(oRet); }
IMMNotificationClientを実装したクラスのヘッダ
#pragma once #include <windows.h> #include <mmdeviceapi.h> #include <functiondiscoverykeys.h> #include <endpointvolume.h> #include <string> class CMMNotificationClient : public IMMNotificationClient { LONG _cRef; IMMDeviceEnumerator* _pEnumerator; public: CMMNotificationClient(IMMDeviceEnumerator* pEnum); ~CMMNotificationClient(); ULONG STDMETHODCALLTYPE AddRef(); ULONG STDMETHODCALLTYPE Release(); HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface); std::string GetDevName(LPCWSTR pwstrDeviceId); HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId); HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId); HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId); HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState); HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key); };
メイン
抜きさし時のイベントを取るだけなら、メインはこれだけでOK。
参考:メインの全体はこちら
#include <windows.h> #include <mmdeviceapi.h> #include <functiondiscoverykeys.h> #include <endpointvolume.h> #include <string> #include "CMMNotificationClient.h" int main() { HRESULT hr; IMMDeviceEnumerator* pEnum = NULL; IMMDeviceCollection* pCollection = NULL; UINT deviceCount = 0; // 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); while (1) { Sleep(100000); } //// 後処理系 if (pEnum) pEnum->Release(); // COMの後処理(COMのお作法) CoUninitialize(); }
備考
「COM」というしくみを使っているやり方のようだが、COMが全然わかってない。
今回作った「CMMNotificationClient」クラスが、
- ULONG STDMETHODCALLTYPE AddRef();
- ULONG STDMETHODCALLTYPE Release();
- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvInterface);
を実装しないといけないのも、COMのお作法らしい。
オーディオの話とは別に、COMについても調べたい。
参考
https://learn.microsoft.com/ja-jp/windows/win32/coreaudio/device-events
前回の自分の記事(PCに接続されているマイク(オーディオデバイス)を取得し、音量設定をする(EnumAudioEndpoints))