potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C++20&Win API ビデオキャプチャデバイスの情報を取得する

DirectShowを使ってビデオキャプチャバイスの情報を取得するコードです。ネット上で様々な方がコードを公開しているコードをWIL対応にしたようなコードです。一部の情報はMicrosoft Docsの「キャプチャ デバイスの選択」を参考にしています。

#include <string>

#define STRICT
#define NOMINMAX
#include <Windows.h>
#include <dshow.h>
#include <propvarutil.h>
#pragma comment(lib, "Strmiids.lib")
#pragma comment(lib, "propsys.lib")

#include <wil/com.h>

namespace winutil
{
    // AM_MEDIA_TYPE構造体を解放します。
    // https://learn.microsoft.com/en-us/previous-versions/ms783299(v=vs.85)
    // https://learn.microsoft.com/en-us/previous-versions/ms783692(v=vs.85)
    void FreeAMMediaType(AM_MEDIA_TYPE* pmt)
    {
        if (pmt == nullptr) return;

        if (pmt->cbFormat != 0)
            CoTaskMemFree(pmt->pbFormat);
        if (pmt->pUnk != nullptr)
            pmt->pUnk->Release();
        CoTaskMemFree(pmt);
    }

    using unique_am_media_type = wil::unique_any<AM_MEDIA_TYPE*, decltype(FreeAMMediaType), &FreeAMMediaType>;

    // プロパティバッグから値を読み込みます。エラー時は空の値を返します。
    wil::unique_variant Read(
        wil::com_ptr<IPropertyBag>& propBag,
        LPCWSTR propertyName,
        IErrorLog* perrLog = nullptr)
    {
        wil::unique_variant v;
        if (FAILED(propBag->Read(propertyName, &v, perrLog)))
            return {};
        return v;
    }

    // VARIANT型を文字列へ変換します。
    std::wstring VariantToWS(const VARIANT& v)
    {
        LPWSTR p;
        if (FAILED(::VariantToStringAlloc(v, &p)))
            return {};
        return p;
    }
}

#include <iostream>
#include <format>

int main()
{
    std::wcout.imbue(std::locale("", std::locale::ctype));

    auto coinit{wil::CoInitializeEx(COINIT_APARTMENTTHREADED)};
    if (!coinit) return -1;

    auto devEnum{wil::CoCreateInstance<ICreateDevEnum>(
        CLSID_SystemDeviceEnum, CLSCTX_INPROC_SERVER)};

    // ループ内で繰り返し使うので準備しておく。
    auto captureGraphBuilder2{wil::CoCreateInstance<ICaptureGraphBuilder2>(
        CLSID_CaptureGraphBuilder2, CLSCTX_INPROC_SERVER)};

    // ビデオキャプチャデバイスを列挙する。
    wil::com_ptr<IEnumMoniker> enumMoniker;
    THROW_IF_FAILED(devEnum->CreateClassEnumerator(
        CLSID_VideoInputDeviceCategory, enumMoniker.put(), 0));
    wil::com_ptr<IMoniker> moniker;
    while (enumMoniker->Next(1, moniker.put(), nullptr) == S_OK)
    {
        // 確認用に表示名を取得する。
        wil::unique_cotaskmem_string displayName;
        moniker->GetDisplayName(nullptr, nullptr, displayName.put());
        std::wcout << wil::string_get_not_null(displayName) << std::endl;

        // プロパティを取得する。
        wil::com_ptr<IPropertyBag> propBag;
        moniker->BindToStorage(nullptr, nullptr, IID_PPV_ARGS(propBag.put()));
        std::wcout << std::format(L" FriendlyName: {}\n Description: {}\n DevicePath: {}\n",
            winutil::VariantToWS(winutil::Read(propBag, L"FriendlyName")),
            winutil::VariantToWS(winutil::Read(propBag, L"Description")),
            winutil::VariantToWS(winutil::Read(propBag, L"DevicePath")));

        // DirectShowキャプチャフィルターを取得する。
        wil::com_ptr<IBaseFilter> baseFilter;
        if (HRESULT hr; FAILED(hr = moniker->BindToObject(
            nullptr, nullptr, IID_PPV_ARGS(baseFilter.put()))))
        {
            if (hr == E_ACCESSDENIED)
            {
                std::wcout << L"カメラにアクセスできませんでした。"
                    L"システム設定でデスクトップアプリのアクセスがオフになっていませんか?\n";
            }
            continue;
        }

        wil::com_ptr<IAMStreamConfig> amStreamConfig;
        THROW_IF_FAILED(captureGraphBuilder2->FindInterface(
            &PIN_CATEGORY_CAPTURE, nullptr, baseFilter.get(),
            IID_PPV_ARGS(amStreamConfig.put())));

        int capCount;
        int capSize;
        THROW_IF_FAILED(amStreamConfig->GetNumberOfCapabilities(&capCount, &capSize));
        if (capSize == sizeof(VIDEO_STREAM_CONFIG_CAPS))
        {
            for (int icap = 0; icap < capCount; icap++)
            {
                VIDEO_STREAM_CONFIG_CAPS caps;
                winutil::unique_am_media_type mediaType;
                THROW_IF_FAILED(amStreamConfig->GetStreamCaps(
                    icap, mediaType.put(), reinterpret_cast<BYTE*>(&caps)));

                if (mediaType.get()->majortype == MEDIATYPE_Video
                    && mediaType.get()->formattype == FORMAT_VideoInfo
                    && mediaType.get()->cbFormat == sizeof(VIDEOINFOHEADER))
                {
                    auto videoInfoHeader = reinterpret_cast<const VIDEOINFOHEADER*>(
                        mediaType.get()->pbFormat);
                    auto& bmih = videoInfoHeader->bmiHeader;
                    // AvgTimePerFrameは100 ns当たりの平均フレーム持続時間
                    auto fps = 1e9 / 100 / videoInfoHeader->AvgTimePerFrame;
                    std::wcout << std::format(L"{}×{} {}ビット {:.2f}fps\n",
                        bmih.biWidth, bmih.biHeight, bmih.biBitCount, fps);
                }
            }
        }

        std::wcout << std::endl;
    }

    return 0;
}