Microsoft MVP성태의 닷넷 이야기
닷넷: 2253. C# - Video Capture 장치(Camera) 열거 및 지원 포맷 조회 [링크 복사], [링크+제목 복사],
조회: 9078
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

C# - Video Capture 장치(Camera) 열거 및 지원 포맷 조회

기왕에 Microsoft Media Foundation의 PInvoke 호출까지 알아봤으니,

C# - 인터페이스 타입의 다중 포인터를 인자로 갖는 C/C++ 함수 연동
; https://www.sysnet.pe.kr/2/0/13607

아래 문서에 나오는 예제를 C#으로 완성해 보겠습니다. ^^

Enumerating Video Capture Devices
; https://learn.microsoft.com/en-us/windows/win32/medfound/enumerating-video-capture-devices

How to Set the Video Capture Format
; https://learn.microsoft.com/en-us/windows/win32/medfound/how-to-set-the-video-capture-format

우선, Video 장치의 수를 먼저 알아볼 텐데요, 관련 COM 인터페이스만 적절하게 정의를 맞춰주면 다음과 같이 구할 수 있습니다.

public static unsafe uint GetVideoDeviceCount()
{
    uint pcSourceActivate = 0;

    IMFAttributes? pAttributes = null;
    IMFActivate[]? ppDevices = null;

    // 1. IMFAttributes 개체를 구해,
    NativeMethods.MFCreateAttributes(out pAttributes, 1);

    try
    {
        // 2. Video 장치로 대상을 설정하고, 
        pAttributes.SetGuid(MFGuid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_GUID, MFGuid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);

        // 3. 장치에 대응하는 IMFActivate 인스턴스 배열을 구함 (pcSourceActivate가 장치의 개수를 의미)
        NativeMethods.MFEnumDeviceSources(pAttributes, out ppDevices, out pcSourceActivate);
    }
    finally
    {
        // 자원 해제
        if (ppDevices != null)
        {
            for (uint i = 0; i < pcSourceActivate; i++)
            {
                Marshal.ReleaseComObject(ppDevices[i]);
            }
        }

        if (pAttributes != null)
        {
            Marshal.ReleaseComObject(pAttributes);
        }
    }

    return pcSourceActivate;
}

설치한 비디오 Camera 수를 알았으면 이제 인덱스 별로 장치에 대한 IMFMediaSource 인터페이스를 구할 수 있습니다.

// Enumerating Video Capture Devices
// ; https://learn.microsoft.com/en-us/windows/win32/medfound/enumerating-video-capture-devices

uint captureDeviceCount = GetVideoDeviceCount();

for (int i = 0; i < captureDeviceCount; i++)
{
    IMFMediaSource mediaSource = GetMediaSource(i, out string symbolicLink, out string deviceName);

    // ... Video Camera에 대한 mediaSource 인터페이스 사용
}

public static IMFMediaSource GetMediaSource(int deviceIndex, out string symbolicLink, out string deviceName)
{
    IMFAttributes? pAttributes = null;
    IMFActivate[]? ppDevices = null;
    IMFMediaSource? pSource = null;
    uint pcSourceActivate = 0;
    symbolicLink = string.Empty;

    // 1. GetVideoDeviceCount 메서드에서 썼던 코드를 재사용, 즉, 연결 중인 Video 장치를 모두 가져와서,
    NativeMethods.MFCreateAttributes(out pAttributes, 1);

    try
    {
        pAttributes.SetGuid(MFGuid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_GUID, MFGuid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
        NativeMethods.MFEnumDeviceSources(pAttributes, out ppDevices, out pcSourceActivate);

        if (deviceIndex >= pcSourceActivate)
        {
            throw new ArgumentOutOfRangeException($"MediaDevices[{nameof(deviceIndex)}]");
        }

        // 2. 그 장치 목록 배열에서 deviceIndex를 지정한 장치의 정보를 조회
        ppDevices[deviceIndex].GetAllocatedString(MFGuid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK_GUID,
            out symbolicLink, out _);

        ppDevices[deviceIndex].GetAllocatedString(MFGuid.MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, out deviceName, out _);

        // 3. 해당 장치의 IMFMediaSource 인터페이스를 반환
        ppDevices[deviceIndex].ActivateObject(typeof(IMFMediaSource).GUID, out pSource);
    }
    finally
    {
        if (ppDevices != null)
        {
            for (uint i = 0; i < pcSourceActivate; i++)
            {
                Marshal.ReleaseComObject(ppDevices[i]);
            }
        }

        if (pAttributes != null)
        {
            Marshal.ReleaseComObject(pAttributes);
        }
    }

    return pSource;
}

참고로, 위에서 구한 symbolicLink, deviceName을 출력해 보면 이런 식으로 나옵니다.

symbolicLink: \\?\USB#VID_046D&...[생략]...\{bbefb6c7-...[생략]...724083}: 
deviceName: HD Pro Webcam C920

그러고 보니 언젠가 이와 유사한 정보를 구했던 것 같습니다. ^^

데스크톱 윈도우 응용 프로그램에서 UWP 라이브러리를 이용한 비디오 장치 열람하는 방법
; https://www.sysnet.pe.kr/2/0/11284

위의 글에서 "DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);" 코드로 구했던 DeviceInformation 타입의 Id와 Name이 정확히 그 값에 해당합니다.




마지막으로, IMFMediaSource를 구했으면 이제 그 장치에 대한 정보를 조회할 수 있습니다. 일례로 해당 장치가 지원하고 있는 Capture Format을 열거하는 것이 가능한데요, 대충 다음과 같은 코드로 가능합니다.

// How to Set the Video Capture Format
// ; https://learn.microsoft.com/en-us/windows/win32/medfound/how-to-set-the-video-capture-format

internal static void GetCaptureFormats(IMFMediaSource pSource)
{
    IMFPresentationDescriptor? pPD = null;
    IMFStreamDescriptor? pSD = null;
    IMFMediaTypeHandler? pHandler = null;

    try
    {
        pSource.CreatePresentationDescriptor(out pPD);
        pPD.GetStreamDescriptorByIndex(0, out bool pfSelected, out pSD);
        pSD.GetMediaTypeHandler(out pHandler);

        // 1. 장치에서 지원하는 Capture Format 수를 구하고,
        pHandler.GetMediaTypeCount(out uint cTypes);

        // 2. 해당 Capture Format을 구성하는 속성들을 조회할 수 있는 IMFMediaType을 가져와,
        for (uint i = 0; i < cTypes; i++)
        {
            pHandler.GetMediaTypeByIndex(i, out IMFMediaType ppType);

            try
            {
                // 3. Capture Format을 설명하는 각각의 값을 출력
                DebugLogMediaType(ppType);
            }
            finally
            {
                if (ppType != null)
                {
                    Marshal.ReleaseComObject(ppType);
                }
            }
        }
    }
    finally
    {
        if (pPD != null)
        {
            Marshal.ReleaseComObject(pPD);
        }

        if (pSD != null)
        {
            Marshal.ReleaseComObject(pSD);
        }

        if (pHandler != null)
        {
            Marshal.ReleaseComObject(pHandler);
        }
    }
}

여기서 좀 난관이 있는데요, IMFMediaType으로 조회하는 필드의 값 타입이 PropVariant이므로,

PROPVARIANT structure (propidlbase.h)
; https://learn.microsoft.com/en-us/windows/win32/api/propidlbase/ns-propidlbase-propvariant

직접 만들기 귀찮으니 PropVariant를 감싼 적절한 소스코드를 찾아,

Programming/C#/Windows API Code Pack 1.1/source/WindowsAPICodePack/Core/PropertySystem
; https://github.com/jlnewton87/Programming/blob/master/C%23/Windows%20API%20Code%20Pack%201.1/source/WindowsAPICodePack/Core/PropertySystem/PropVariant.cs

활용하면 이런 식으로 DebugLogMediaType 코드를 작성할 수 있습니다.

private static void DebugLogMediaType(IMFMediaType pType)
{
    uint count = 0;

    // 1. Capture Format을 구성하는 필드의 수를 반환
    pType.GetCount(out count);

    // 2. 개별 필드에 대한 값을 구해 출력
    for (uint i = 0; i < count; i ++)
    {
        IMFAttributes pAttr = pType as IMFAttributes;
        DebugLogAttributeValueByIndex(pAttr, i);
    }
}

private static void DebugLogAttributeValueByIndex(IMFAttributes pAttr, uint i)
{
    PropVariant var = new PropVariant();
    pAttr.GetItemByIndex(i, out Guid guid, var);

    string guidName = MFGuid.GetName(guid);

    Console.Write($"{guidName}: ");
    SpecialCaseAttributeValue(guid, var);

    if (hr == HRESULT.S_FALSE)
    {
        switch (var.VarType)
        {
            case VarEnum.VT_UI4:
            case VarEnum.VT_R8:
                Console.Write(var.Value);
                break;

            case VarEnum.VT_CLSID:
                Console.Write(MFGuid.GetName((Guid)var.Value));
                break;

            case VarEnum.VT_LPWSTR:
                Console.Write(var.Value);
                break;

            case VarEnum.VT_VECTOR | VarEnum.VT_UI1:
                Console.Write($"<<byte array>>");
                break;

            case VarEnum.VT_UNKNOWN:
                Console.Write($"IUnknown");
                break;
        }
    }

    Console.WriteLine();
}




테스트해보면, Logitech HD Pro Webcam C920 제품의 경우 573개의 Capture Foramt을 지원하는데요, 개별 포맷에 대한 필드를 보면 이런 출력 결과를 얻을 수 있습니다.

Format: #1
MF_MT_MAJOR_TYPE        MFMediaType_Video
MF_MT_VIDEO_LIGHTING    3
MF_MT_DEFAULT_STRIDE    1600
MF_MT_VIDEO_CHROMA_SITING       6
MF_MT_AM_FORMAT_TYPE    {F72A76A0-EB0A-11D0-ACE4-0000C0CC16BA}
MF_MT_FIXED_SIZE_SAMPLES        1
MF_MT_VIDEO_NOMINAL_RANGE       2
MF_MT_FRAME_RATE        30 x 1
MF_MT_PIXEL_ASPECT_RATIO        1 x 1
MF_MT_ALL_SAMPLES_INDEPENDENT   1
MF_MT_FRAME_RATE_RANGE_MIN      30 x 1
MF_MT_SAMPLE_SIZE       716800
MF_MT_VIDEO_PRIMARIES   2
MF_MT_INTERLACE_MODE    2
MF_MT_FRAME_RATE_RANGE_MAX      30 x 1
MF_MT_SUBTYPE   MFVideoFormat_YUY2

...[생략]...

Format: #573
MF_MT_FRAME_SIZE: 1920 x 1080
MF_MT_AVG_BITRATE: 995328000
MF_MT_YUV_MATRIX: 2
MF_MT_MAJOR_TYPE: MFMediaType_Video
MF_MT_VIDEO_LIGHTING: 3
MF_MT_CHROMA_SITING: 6
MF_MT_AM_FORMAT_TYPE: WAVEFORMAT_VIDEOINFOHEADER2
MF_MT_FIXED_SIZE_SAMPLES: 1
MF_MT_VIDEO_NOMINAL_RANGE: 2
MF_MT_FRAME_RATE: 5 x 1
MF_MT_PIXEL_ASPECT_RATIO: 1 x 1
MF_MT_ALL_SAMPLES_INDEPENDENT: 1
MF_MT_FRAME_RATE_RANGE_MIN: 5 x 1
MF_MT_SAMPLE_SIZE: 4147200
MF_MT_VIDEO_PRIMARIES: 2
MF_MT_INTERLACE_MODE: 2
MF_MT_FRAME_RATE_RANGE_MAX: 5 x 1
MF_MT_SUBTYPE: MFVideoFormat_H264

암튼, 오디오와 비디오 세계는 그동안 쌓인 역사가 오래돼서 그런지 꽤나 복잡한 듯합니다. ^^

첨부 파일은 이 글의 예제 코드를 포함하지만, 기본적인 Interop만 포함하고 있으므로 활용 수준으로는 적합하지 않습니다. 그런 목적으로는 Nuget에 있는 MediaFoundation 패키지가 도움이 될 수 있습니다.

MediaFoundation
; https://www.nuget.org/packages/MediaFoundation




아래의 질문이 갑자기 떠오르는데요,

OpenCV 이용 해상도 설정 질문 입니다..
; https://www.sysnet.pe.kr/3/0/5317

아마도 윈도우 10의 기본 앱에서는 "How to Set the Video Capture Format" 글에 나온 "IMFMediaTypeHandler.SetCurrentMediaType" 함수를 호출해 2560x1440 모드로 설정을 한 것이 아닐까... 하는 예상을 해봅니다. ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 5/1/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  131  132  [133]  134  135  ...
NoWriterDateCnt.TitleFile(s)
1731정성태8/11/201427086개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
1730정성태8/11/201422168개발 환경 구성: 234. Royal TS의 터미널(Terminal) 연결에서 한글이 깨지는 현상 해결 방법
1729정성태8/11/201418226오류 유형: 236. SqlConnection - The requested Performance Counter is not a custom counter, it has to be initialized as ReadOnly.
1728정성태8/8/201430295.NET Framework: 453. C# - 오피스 파워포인트(Powerpoint) 파일을 WinForm에서 보는 방법파일 다운로드1
1727정성태8/6/201420524오류 유형: 235. SignalR 오류 메시지 - Counter 'Messages Bus Messages Published Total' does not exist in the specified Category. [2]
1726정성태8/6/201419402오류 유형: 234. IIS Express에서 COM+ 사용 시 SecurityException - "Requested registry access is not allowed" 발생
1725정성태8/6/201421357오류 유형: 233. Visual Studio 2013 Update3 적용 후 Microsoft.VisualStudio.Web.PageInspector.Runtime 모듈에 대한 FileNotFoundException 예외 발생
1724정성태8/5/201426103.NET Framework: 452. .NET System.Threading.Thread 개체에서 Native Thread Id를 구하는 방법 - 두 번째 이야기 [1]파일 다운로드1
1723정성태7/29/201458376개발 환경 구성: 233. DirectX 9 예제 프로젝트 빌드하는 방법 [3]파일 다운로드1
1722정성태7/25/201421068오류 유형: 232. IIS 500 Internal Server Error - NTFS 암호화된 폴더에 웹 애플리케이션이 위치한 경우
1721정성태7/24/201424085.NET Framework: 451. 함수형 프로그래밍 개념 - 리스트 해석(List Comprehension)과 순수 함수 [2]
1720정성태7/23/201422082개발 환경 구성: 232. C:\WINDOWS\system32\LogFiles\HTTPERR 폴더에 로그 파일을 남기지 않는 설정
1719정성태7/22/201426043Math: 13. 동전을 여러 더미로 나누는 경우의 수 세기(Partition Number) - 두 번째 이야기파일 다운로드1
1718정성태7/19/201435303Math: 12. HTML에서 수학 관련 기호/수식을 표현하기 위한 방법 - MathJax.js [4]
1716정성태7/17/201435019개발 환경 구성: 231. PC 용 무료 안드로이드 에뮬레이터 - genymotion
1715정성태7/13/201430610기타: 47. 운영체제 종료 후에도 USB 외장 하드의 전원이 꺼지지 않는 경우 [3]
1714정성태7/11/201420891VS.NET IDE: 92. Visual Studio 2013을 지원하는 IL Support 확장 도구
1713정성태7/11/201444613Windows: 98. 윈도우 시스템 디스크 용량 확보를 위한 "Package Cache" 폴더 이동 [1]
1712정성태7/10/201432878.NET Framework: 450. 영문 윈도우에서 C# 콘솔 프로그램의 유니코드 출력 방법 [3]
1711정성태7/10/201438069Windows: 97. cmd.exe 창에서 사용할 폰트를 추가하는 방법 [1]
1710정성태7/8/201430583개발 환경 구성: 230. 유니코드의 Surrogate Pair, Supplementary Characters가 뭘까요?파일 다운로드2
1709정성태7/8/201427405VS.NET IDE: 91. Visual Studio에서 32/64비트 IIS Express 실행하는 방법
1708정성태7/7/201424767VS.NET IDE: 90. Visual Studio - 사용자 정의 정적 분석 규칙 만드는 방법 [3]파일 다운로드1
1707정성태7/4/201423034.NET Framework: 449. C#에서 C++로 VARIANT 넘겨주는 방법파일 다운로드1
1706정성태7/3/201421436.NET Framework: 448. .NET SmartClient 컨트롤을 윈도우 8/2012에서 활성화하는 방법파일 다운로드1
1705정성태7/2/201435074VC++: 78. 보이어-무어(Boyer-Moore) 알고리즘이 정말 빠를까? [6]파일 다운로드1
... 121  122  123  124  125  126  127  128  129  130  131  132  [133]  134  135  ...