1. 程式人生 > 實用技巧 >[C++]DirectShow檢測音視訊輸入裝置及其採集引數

[C++]DirectShow檢測音視訊輸入裝置及其採集引數

官方文件:https://docs.microsoft.com/zh-cn/windows/win32/directshow/directshow

建立CLR類庫專案(CSharpDirectShow),編寫託管的DirectShow類庫,右鍵專案屬性-->連結器--> 輸入-->附加依賴項;新增靜態庫檔案Strmiids.lib和Quartz.lib;

定義標頭檔案CSharpDirectShow.h,包含標頭檔案dshow.h,定義如下方法:

#pragma once

#include <dshow.h>

using namespace System;
using
namespace System::Runtime::InteropServices; public ref class DirectShow { public: /// <summary> /// 在當前執行緒上初始化COM庫,並將併發模型標識為單執行緒單元(STA) /// </summary> /// <returns></returns> static Boolean ComInit(); /// <summary> /// 關閉當前執行緒上的COM庫,解除安裝該執行緒載入的所有DLL,釋放該執行緒維護的所有其他資源,並強制關閉該執行緒上的所有RPC連線
/// </summary> static void ComUinit(); /// <summary> /// 獲取視訊輸入裝置 /// </summary> /// <param name="devices"></param> /// <returns></returns> static Int32 GetVideoInputDevices([Out]array<VideoInputDsDevice^>^% devices); /// <summary> /// 獲取音訊輸入裝置
/// </summary> /// <param name="device"></param> /// <returns></returns> static Int32 GetAudioInputDevices([Out]array<AudioInputDsDevice^>^% device); };

其中,初始化COM庫只能在STA執行緒呼叫,控制檯程式直接呼叫會返回失敗,在Winform和WPF等應用程式應該在第一次使用時初始化COM庫;
當前執行緒初始化COM庫和關閉COM庫:

Boolean DirectShow::ComInit()
{
    HRESULT hr = CoInitialize(NULL);
    return hr == S_OK || hr == S_FALSE;
}

void DirectShow::ComUinit()
{
    CoUninitialize();
}

一、列舉視訊輸入裝置,參考:https://docs.microsoft.com/zh-cn/windows/win32/directshow/selecting-a-capture-device

1、建立視訊輸入裝置的列舉器IEnumMoniker

HRESULT DirectShow::EnumerateDevices(REFGUID category, IEnumMoniker** ppEnum)
{
    // 建立系統裝置列舉器
    ICreateDevEnum* pDevEnum;
    HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));

    if (SUCCEEDED(hr))
    {
        // 為類別建立列舉數.
        hr = pDevEnum->CreateClassEnumerator(category, ppEnum, 0);

        if (hr == S_FALSE)
            hr = VFW_E_NOT_FOUND;  // 類別是空的,視為錯誤

        pDevEnum->Release();
    }
    return hr;
}

Int32 DirectShow::GetVideoInputDevices([Out]array<VideoInputDsDevice^>^% devices)
{
    devices = nullptr;

    IEnumMoniker* pEnum;
    HRESULT hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &pEnum);        // 建立視訊輸入裝置列舉器

    if (FAILED(hr))
        return hr;

    hr = EnumerateVideoInputDevices(pEnum, devices);    // 列舉視訊輸入裝置
    pEnum->Release();
    return hr;
}

2、呼叫IEnumMoniker::Next()方法列舉裝置,呼叫IPropertyBag::Read方法讀取裝置屬性,以獲取裝置的友好名稱和裝置標識字串:

HRESULT DirectShow::EnumerateVideoInputDevices(IEnumMoniker* pEnum, [Out]array<VideoInputDsDevice^>^% devices)
{
    HRESULT hr = S_FALSE;
    devices = nullptr;
    List<VideoInputDsDevice^>^ list = gcnew List<VideoInputDsDevice^>();

    IMoniker* pMoniker;
    while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
    {
        IPropertyBag* pPropBag;
        hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
        if (FAILED(hr))
        {
            pMoniker->Release();
            continue;
        }

        VideoInputDsDevice^ device = gcnew VideoInputDsDevice();

        VARIANT var;
        VariantInit(&var);

        // 獲取裝置友好名
        hr = pPropBag->Read(L"FriendlyName", &var, 0);

        if (FAILED(hr))
            hr = pPropBag->Read(L"Description", &var, 0);

        if (SUCCEEDED(hr))
        {
            device->FriendlyName = System::String(var.bstrVal).ToString();
            VariantClear(&var);
        }

        // 獲取裝置Moniker名
        LPOLESTR pOleDisplayName = reinterpret_cast<LPOLESTR>(CoTaskMemAlloc(MAX_MONIKER_NAME_LENGTH * 2));
        hr = pMoniker->GetDisplayName(NULL, NULL, &pOleDisplayName);

        if (SUCCEEDED(hr))
        {
            device->MonikerName = System::String(pOleDisplayName).ToString();
            array<VideoParams^>^ params;
            hr = EnumerateVideoParams(pMoniker, params);    // 列舉裝置的採集引數
            if (SUCCEEDED(hr))
                device->Params = params;
        }

        CoTaskMemFree(pOleDisplayName);

        list->Add(device);
        pPropBag->Release();
        pMoniker->Release();
    }

    devices = list->ToArray();
    return S_OK;
}

3、列舉視訊輸入裝置的採集引數:

HRESULT DirectShow::EnumerateVideoParams(IMoniker* pMoniker, [Out]array<VideoParams^>^% params)
{
    params = nullptr;
    IBaseFilter* pFilter;
    HRESULT    hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);

    if (FAILED(hr))
        return hr;

    IEnumPins* pinEnum;
    hr = pFilter->EnumPins(&pinEnum);

    if (FAILED(hr)) {
        pFilter->Release();
        return hr;
    }

    List<VideoParams^>^ list = gcnew List<VideoParams^>();
    IPin* pPins;
    while (pinEnum->Next(1, &pPins, NULL) == S_OK)
    {
        PIN_INFO pinInfo;
        hr = pPins->QueryPinInfo(&pinInfo);

        if (FAILED(hr) || pinInfo.dir != PINDIR_OUTPUT)
        {
            pPins->Release();
            continue;
        }

        IEnumMediaTypes* mtEnum;
        hr = pPins->EnumMediaTypes(&mtEnum);

        if (FAILED(hr))
        {
            pPins->Release();
            continue;
        }

        AM_MEDIA_TYPE* mt;
        while (mtEnum->Next(1, &mt, NULL) == S_OK)
        {
            VideoParams^ param = nullptr;
            if (mt->formattype == FORMAT_VideoInfo)
            {
                VIDEOINFOHEADER* pVih = reinterpret_cast<VIDEOINFOHEADER*>(mt->pbFormat);
                param = gcnew VideoParams();
                param->FrameWidth = pVih->bmiHeader.biWidth;
                param->FrameHeight = pVih->bmiHeader.biHeight;
                param->AverageFrameRate = pVih->AvgTimePerFrame == 0 ? 0 : 10000000 / pVih->AvgTimePerFrame;
            }
            else if (mt->formattype == FORMAT_VideoInfo2) {
                VIDEOINFOHEADER2* pVih = reinterpret_cast<VIDEOINFOHEADER2*>(mt->pbFormat);
                param = gcnew VideoParams();
                param->FrameWidth = pVih->bmiHeader.biWidth;
                param->FrameHeight = pVih->bmiHeader.biHeight;
                param->AverageFrameRate = pVih->AvgTimePerFrame == 0 ? 0 : 10000000 / pVih->AvgTimePerFrame;
            }

            if (param && param->AverageFrameRate > 1)
            {
                Boolean isExit = false;
                for each (VideoParams ^ item in list)
                {
                    if (item->FrameWidth == param->FrameWidth && item->FrameHeight == param->FrameHeight && item->AverageFrameRate == param->AverageFrameRate)
                    {
                        isExit = true;
                        break;
                    }
                }
                if (!isExit)
                    list->Add(param);
            }
        }
        pPins->Release();
    }

    pFilter->Release();

    params = list->ToArray();
    return S_OK;
}

呼叫程式碼及結果:

var ret = DirectShow.GetVideoInputDevices (out VideoInputDsDevice[] videoInputDevices);

if (ret == 0) {
    Console.WriteLine ("視訊輸入裝置:");

    foreach (var videoInputDevice in videoInputDevices) {
        Console.WriteLine ($"{videoInputDevice.FriendlyName}\t{videoInputDevice.MonikerName}");

        if (videoInputDevice.Params.Length > 0) {
            Console.WriteLine ("畫素寬度\t畫素高度\t1秒平均幀數");

            foreach (var param in videoInputDevice.Params) {
                Console.WriteLine ($"{param.FrameWidth}\t{param.FrameHeight}\t{param.AverageFrameRate}");
            }
        }

        Console.WriteLine ();
    }
}

二、列舉音訊輸入裝置,參考https://docs.microsoft.com/zh-cn/windows/win32/directshow/selecting-a-capture-device,過程和視訊一樣:

ret = DirectShow.GetAudioInputDevices (out AudioInputDsDevice[] audioInputDevices);

if (ret == 0) {
    Console.WriteLine ("音訊輸入裝置:");

    foreach (var audioInputDevice in audioInputDevices) {
        Console.WriteLine ($"{audioInputDevice.FriendlyName}\t{audioInputDevice.MonikerName}");

        if (audioInputDevice.Params.Length > 0) {
            Console.WriteLine ("音訊格式\t通道數\t取樣速率\t塊對齊\t位數");

            foreach (var param in audioInputDevice.Params) {
                Console.WriteLine ($"{param.Format}\t{param.Channels}\t{param.SampleRate}\t{param.BlockAlign}\t{param.BitsPerSample}");
            }
        }

        Console.WriteLine ();
    }
}

程式碼已上傳至Github:https://github.com/LowPlayer/CameraCapture