DirectShowデバイスの列挙

COM経由で。

さて、先日

directshow capture device list enumerate c++ builder resolution framerate

等と長めのキーワードを入れて検索してみたら、なにやらBing君経由でCopilot君が”口を挟んできました”これ自体は珍しいことではありませんよね。なにやらコードを示してきました。Copilot君も日夜研鑽しているので、読者が同じことを試しても同じ結果が得られるとは限らないことには注意しましょうね。(まさかmsnアカウントで区別して情報収集してるとか?その節はあるんだよね。)Copilot君の案は、

#include <dshow.h>
#include <vector>
#include <string>
#include <iostream>

// Helper function to release COM objects safely
template <typename T>
void SafeRelease(T** ppT) {
    if (*ppT) {
        (*ppT)->Release();
        *ppT = nullptr;
    }
}

// Function to enumerate video capture devices
void EnumerateCaptureDevices() {
    CoInitialize(nullptr); // Initialize COM

    ICreateDevEnum* pDevEnum = nullptr;
    IEnumMoniker* pEnum = nullptr;

    // Create the System Device Enumerator
    if (SUCCEEDED(CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pDevEnum))) {
        // Create an enumerator for video capture devices
        if (SUCCEEDED(pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0))) {
            IMoniker* pMoniker = nullptr;
            while (pEnum->Next(1, &pMoniker, nullptr) == S_OK) {
                IPropertyBag* pPropBag = nullptr;
                if (SUCCEEDED(pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void**)&pPropBag))) {
                    VARIANT varName;
                    VariantInit(&varName);

                    // Get the friendly name of the device
                    if (SUCCEEDED(pPropBag->Read(L"FriendlyName", &varName, nullptr))) {
                        std::wcout << L"Device: " << varName.bstrVal << std::endl;
                        VariantClear(&varName);
                    }

                    SafeRelease(&pPropBag);
                }

                // Bind to the filter to enumerate supported resolutions and frame rates
                IBaseFilter* pFilter = nullptr;
                if (SUCCEEDED(pMoniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, (void**)&pFilter))) {
                    IAMStreamConfig* pStreamConfig = nullptr;
                    IEnumPins* pEnumPins = nullptr;

                    if (SUCCEEDED(pFilter->EnumPins(&pEnumPins))) {
                        IPin* pPin = nullptr;
                        while (pEnumPins->Next(1, &pPin, nullptr) == S_OK) {
                            if (SUCCEEDED(pPin->QueryInterface(IID_IAMStreamConfig, (void**)&pStreamConfig))) {
                                int iCount = 0, iSize = 0;
                                if (SUCCEEDED(pStreamConfig->GetNumberOfCapabilities(&iCount, &iSize))) {
                                    for (int i = 0; i < iCount; ++i) {
                                        VIDEO_STREAM_CONFIG_CAPS scc;
                                        AM_MEDIA_TYPE* pmt = nullptr;

                                        if (SUCCEEDED(pStreamConfig->GetStreamCaps(i, &pmt, (BYTE*)&scc))) {
                                            if (pmt->formattype == FORMAT_VideoInfo) {
                                                VIDEOINFOHEADER* pVih = (VIDEOINFOHEADER*)pmt->pbFormat;
                                                std::wcout << L"  Resolution: " << pVih->bmiHeader.biWidth << L"x" << pVih->bmiHeader.biHeight
                                                           << L", Frame Rate: " << 10000000.0 / pVih->AvgTimePerFrame << L" fps" << std::endl;
                                            }
                                            CoTaskMemFree(pmt);
                                        }
                                    }
                                }
                                SafeRelease(&pStreamConfig);
                            }
                            SafeRelease(&pPin);
                        }
                        SafeRelease(&pEnumPins);
                    }
                    SafeRelease(&pFilter);
                }
                SafeRelease(&pMoniker);
            }
        }
        SafeRelease(&pEnum);
    }
    SafeRelease(&pDevEnum);

    CoUninitialize(); // Uninitialize COM
}

int main() {
    EnumerateCaptureDevices();
    return 0;
}

なんとこれがちゃんと動きます。(珍しいことですね。ま、Builderの部分は考慮していないので、Visual C++でも動くはずです。)実行例は、

$ bcc32c enum.cpp
Embarcadero C++ 7.70 for Win32 Copyright (c) 2012-2024 Embarcadero Technologies, Inc.
enum.cpp:
Turbo Incremental Link 6.99 Copyright (c) 1997-2024 Embarcadero Technologies, Inc.
docna@Lavie-note MINGW64 ~/Dropbox/enum-device
$ ./enum
Device: Integrated Camera
  Resolution: 1280x720, Frame Rate: 30 fps
  Resolution: 320x180, Frame Rate: 30 fps
  Resolution: 320x240, Frame Rate: 30 fps
  Resolution: 424x240, Frame Rate: 30 fps
  Resolution: 640x480, Frame Rate: 30 fps
  Resolution: 848x480, Frame Rate: 30 fps
  Resolution: 960x540, Frame Rate: 30 fps
  Resolution: 640x360, Frame Rate: 30 fps
  Resolution: 640x360, Frame Rate: 30 fps
  Resolution: 320x180, Frame Rate: 30 fps
  Resolution: 320x240, Frame Rate: 30 fps
  Resolution: 424x240, Frame Rate: 30 fps
  Resolution: 640x480, Frame Rate: 30 fps
Device: Hikvision
  Resolution: 2560x1440, Frame Rate: 25 fps
  Resolution: 2560x1440, Frame Rate: 25 fps
  Resolution: 1920x1080, Frame Rate: 25 fps
  Resolution: 1920x1080, Frame Rate: 25 fps
  Resolution: 1280x720, Frame Rate: 25 fps
  Resolution: 1280x720, Frame Rate: 25 fps
  Resolution: 704x480, Frame Rate: 25 fps
  Resolution: 704x480, Frame Rate: 25 fps
  Resolution: 352x240, Frame Rate: 25 fps
  Resolution: 352x240, Frame Rate: 25 fps

ちゃんと動いてるいるようですね。C++ Builder CEのVclなプログラムから使うので、少し修正する必要がありますね。まずキャプチャーデバイスの情報を保持する構造体を用意しましょう。

#include <vector>
...
...

struct adevice {
	String dname;
	std::vector<String> resolutions;
} aentry;

std::vector<adevice> dlist;

でCopilot君版のEnumerateCaptureDevices()の中を観て、std::wcoutしてるタイミングで、文字列を上記構造体へ代入して、構造体のインスタンスをvectorにpush_backすれば動きそうですね。で、最終版のEnumerateCaptureDevices()は、別ファイルEnumerateCaptureDevices.cppですが、

#include <dshow.h>
#include <vector>
#include <string>
#include <iostream>

struct adevice {
	String dname;
	std::vector<String> resolutions;
};

extern adevice aentry;
extern std::vector<adevice> dlist;

// Helper function to release COM objects safely
template <typename T>
void SafeRelease(T** ppT) {
    if (*ppT) {
        (*ppT)->Release();
        *ppT = nullptr;
    }
}

// Function to enumerate video capture devices
void EnumerateCaptureDevices() {
    CoInitialize(nullptr); // Initialize COM

    ICreateDevEnum* pDevEnum = nullptr;
    IEnumMoniker* pEnum = nullptr;

    // Create the System Device Enumerator
    if (SUCCEEDED(CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pDevEnum))) {
        // Create an enumerator for video capture devices
        if (SUCCEEDED(pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0))) {
            IMoniker* pMoniker = nullptr;
            while (pEnum->Next(1, &pMoniker, nullptr) == S_OK) {
                IPropertyBag* pPropBag = nullptr;
                if (SUCCEEDED(pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void**)&pPropBag))) {
                    VARIANT varName;
                    VariantInit(&varName);

                    // Get the friendly name of the device
                    if (SUCCEEDED(pPropBag->Read(L"FriendlyName", &varName, nullptr))) {
						std::wcout << L"Device: " << varName.bstrVal << std::endl;
						aentry.dname = varName.bstrVal;

                        VariantClear(&varName);
                    }

                    SafeRelease(&pPropBag);
                }

                // Bind to the filter to enumerate supported resolutions and frame rates
                IBaseFilter* pFilter = nullptr;
                if (SUCCEEDED(pMoniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, (void**)&pFilter))) {
                    IAMStreamConfig* pStreamConfig = nullptr;
                    IEnumPins* pEnumPins = nullptr;

                    if (SUCCEEDED(pFilter->EnumPins(&pEnumPins))) {
                        IPin* pPin = nullptr;
                        while (pEnumPins->Next(1, &pPin, nullptr) == S_OK) {
                            if (SUCCEEDED(pPin->QueryInterface(IID_IAMStreamConfig, (void**)&pStreamConfig))) {
                                int iCount = 0, iSize = 0;
                                if (SUCCEEDED(pStreamConfig->GetNumberOfCapabilities(&iCount, &iSize))) {
                                    for (int i = 0; i < iCount; ++i) {
                                        VIDEO_STREAM_CONFIG_CAPS scc;
                                        AM_MEDIA_TYPE* pmt = nullptr;

                                        if (SUCCEEDED(pStreamConfig->GetStreamCaps(i, &pmt, (BYTE*)&scc))) {
                                            if (pmt->formattype == FORMAT_VideoInfo) {
                                                VIDEOINFOHEADER* pVih = (VIDEOINFOHEADER*)pmt->pbFormat;
												std::wcout << L"  Resolution: " << pVih->bmiHeader.biWidth << L"x" << pVih->bmiHeader.biHeight
														   << L", Frame Rate: " << 10000000.0 / pVih->AvgTimePerFrame << L" fps" << std::endl;
												//String astring = IntToStr((int)pVih->bmiHeader.biWidth) + "x" +  IntToStr((int)pVih->bmiHeader.biHeight) + " " + FloatToStrF(10000000.0 / pVih->AvgTimePerFrame, ffFixed, 8, 1);
												String astring = IntToStr((int)pVih->bmiHeader.biWidth) + "x" +  IntToStr((int)pVih->bmiHeader.biHeight);
												aentry.resolutions.push_back(astring);
											}
                                            CoTaskMemFree(pmt);
                                        }
                                    }
                                }
                                SafeRelease(&pStreamConfig);
                            }
                            SafeRelease(&pPin);
                        }
                        SafeRelease(&pEnumPins);
                    }
                    SafeRelease(&pFilter);
                }
				SafeRelease(&pMoniker);
				dlist.push_back(aentry);
                aentry.resolutions.clear();
			}

		}
		SafeRelease(&pEnum);

    }
	SafeRelease(&pDevEnum);

    CoUninitialize(); // Uninitialize COM
}

/*
int main() {
    EnumerateCaptureDevices();
    getchar();
    return 0;
}
*/

しかるべき場所で、

aentry.dname = varName.bstrVal;
...
String astring = IntToStr((int)pVih->bmiHeader.biWidth) + "x" +  IntToStr((int)pVih->bmiHeader.biHeight);
										aentry.resolutions.push_back(astring);
....
dlist.push_back(aentry);
aentry.resolutions.clear();

追加しているだけです。さて、これを使った最終的なプログラムのイメージ(といいながら実像)は、

で、二つのTComboBoxをクリックすると、実際のリストが得られ、そこから使用するデバイス(カメラ)と解像度を選択します。

続いてComboBox2をクリックすると、

得られる解像度(横x縦)が得られますからお好きなものを選択。お好みで、アスペクト比も指定して、

で、Play

上下の黒帯が気になるならば、アクペクト比を正しく設定してPlay。今は手動ですが、自動化できますよね。TPanelのサイズは640x480なので、解像度を640×480にしてアスペクト比を4:3とかにすれば、

心霊写真ではありません。Blurがしつこすぎた気配。

あ、ちなみに一番下のTMemoはデバッグのためのものです。また併せて別記事で紹介済みのLibvlcのwrapperも少しだけ改良してますので、それは次の記事で。

コメント