0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

王国アドベントカレンダーAdvent Calendar 2023

Day 12

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

Posted at

ミーティング中を検知して周囲の人間に知らせる(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");

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

動かしてみる

Something went wrong

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

おわり

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?