ミーティング中を検知して周囲の人間に知らせる(3)
今回は完結編です。
以前作成したマイク使用を感知するプログラムとLEDを光らせるプログラムをつなぎ込んでいきます。
コード
前回からの変更点は、httplib.hというライブラリを追加してHTTPリクエストを飛ばしていることです。
(ライブラリを追加しなければHTTPリクエストすら飛ばせないC++かわいいですね)
#include "httplib.h"
#include <iostream>
#include <array>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <audiopolicy.h>
#include <wrl/client.h>
#include <functiondiscoverykeys_devpkey.h>
#include <psapi.h>
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "Mmdevapi.lib")
// マイクが入力されているかどうかを確認する閾値
// 今回は喋っているかどうかは確認しないので、非常に低い値を設定している(ノイズも拾う)
constexpr float PEEK_VALUE_THRESHOLD = 0.00001f;
// use-mic-checker-serverのhost
constexpr char SERVER_HOST[] = "http://{YOUR_SERVER_HOST}";
std::string lpwstr_to_str(LPWSTR str)
{
int strLength = WideCharToMultiByte(CP_ACP, 0, str, -1, nullptr, 0, nullptr, nullptr);
std::string result(strLength, 0);
WideCharToMultiByte(CP_ACP, 0, str, -1, &result[0],
strLength, nullptr, nullptr);
return result;
}
int main()
{
// COMの初期化
HRESULT hr = CoInitialize(nullptr);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
// デバイスの列挙
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> deviceEnumerator = nullptr;
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&deviceEnumerator);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
// デフォルトのデバイスを取得
Microsoft::WRL::ComPtr<IMMDevice> defaultDevice = nullptr;
hr = deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &defaultDevice);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
// デバイス名を取得
Microsoft::WRL::ComPtr<IPropertyStore> propertyStore = nullptr;
hr = defaultDevice->OpenPropertyStore(STGM_READ, &propertyStore);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
PROPVARIANT deviceName;
PropVariantInit(&deviceName);
hr = propertyStore->GetValue(PKEY_Device_FriendlyName, &deviceName);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
// セッションマネージャーを取得
Microsoft::WRL::ComPtr<IAudioSessionManager2> sessionManager = nullptr;
hr = defaultDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**)&sessionManager);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
httplib::Client httpClient(SERVER_HOST);
while (true)
{
Sleep(1000);
// セッションの列挙
Microsoft::WRL::ComPtr<IAudioSessionEnumerator> sessionEnumerator = nullptr;
hr = sessionManager->GetSessionEnumerator(&sessionEnumerator);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
int sessionCount = 0;
hr = sessionEnumerator->GetCount(&sessionCount);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
bool isMicUsed = false;
// 取得されたセッションに対してPeekを取得し、音声が入力されているかどうかを確認する
for (int i = 0; i < sessionCount; ++i)
{
// セッションコントロールを取得
Microsoft::WRL::ComPtr<IAudioSessionControl> sessionControl = nullptr;
Microsoft::WRL::ComPtr<IAudioSessionControl2> sessionControl2 = nullptr;
hr = sessionEnumerator->GetSession(i, &sessionControl);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
hr = sessionControl->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl2);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
// Peekを取得
Microsoft::WRL::ComPtr<ISimpleAudioVolume> simpleAudioVolume = nullptr;
hr = sessionControl2->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&simpleAudioVolume);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
Microsoft::WRL::ComPtr<IAudioMeterInformation> audioMeter = nullptr;
hr = simpleAudioVolume->QueryInterface(__uuidof(IAudioMeterInformation), (void**)&audioMeter);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
float peakValue = 0.0f;
audioMeter->GetPeakValue(&peakValue);
// 音声が入力されていない場合はスキップ
if (peakValue <= PEEK_VALUE_THRESHOLD)
{
continue;
}
// セッションのプロセス名を取得
DWORD processId;
hr = sessionControl2->GetProcessId(&processId);
if (FAILED(hr))
{
std::cerr << std::system_category().message(hr) << std::endl;
return -1;
}
HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (processHandle == NULL)
{
std::cerr << std::system_category().message(GetLastError()) << std::endl;
return -1;
}
std::array<wchar_t, 1024> processName;
DWORD processNameLength = GetModuleFileNameExW(processHandle, NULL, processName.data(), processName.size());
if (processNameLength == 0)
{
std::cerr << std::system_category().message(GetLastError()) << std::endl;
return -1;
}
isMicUsed = true;
std::cout << "マイクが使用されています: デバイス名=" << lpwstr_to_str(deviceName.pwszVal) << " プロセス名=" << lpwstr_to_str(processName.data()) << " Peek値: " << peakValue << std::endl;
}
if (isMicUsed)
{
httpClient.Get("/led/on");
}
else
{
httpClient.Get("/led/off");
}
}
CoUninitialize();
return 0;
}
httplib.hはこんなかんじで簡単にHTTPリクエストを飛ばすことができます。
httplib::Client httpClient("http://localhst ");
httpClient.Get("/led/on");
また、嬉しいことにヘッダーオンリーです。
コンパイラ・リンカと戦わなくていいので非常に楽ちんですね。
動かしてみる
ちゃんとボイスチャットに入室したときに光って、退出したときに消えてますね。
今回動画は用意していませんが、Meetでも正しく動作していることを確認しています。
おわり
次はラズパイ側の実装をGoにすべく、延長戦をやろうと思います。