ミーティング中を検知して周囲の人間に知らせる(3)

# ミーティング中を検知して周囲の人間に知らせる(3)

今回は完結編です。
以前作成したマイク使用を感知するプログラムとLEDを光らせるプログラムをつなぎ込んでいきます。

## コード

前回からの変更点は、[httplib.h](https://github.com/yhirose/cpp-httplib)というライブラリを追加してHTTPリクエストを飛ばしていることです。
（ライブラリを追加しなければHTTPリクエストすら飛ばせないC++かわいいですね）

``` 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リクエストを飛ばすことができます。

``` cpp
httplib::Client httpClient("http://localhst ");
httpClient.Get("/led/on");
```

また、嬉しいことに**ヘッダーオンリーです。**
コンパイラ・リンカと戦わなくていいので非常に楽ちんですね。

## 動かしてみる

![Something went wrong]()

ちゃんとボイスチャットに入室したときに光って、退出したときに消えてますね。
今回動画は用意していませんが、Meetでも正しく動作していることを確認しています。

## おわり

次はラズパイ側の実装をGoにすべく、延長戦をやろうと思います。