USB DV 비디오 장치 작업

[이 페이지와 연결된 기능인 DirectShow는 레거시 기능입니다. MediaPlayer, IMFMediaEngine 및 Media Foundation의 오디오/비디오 캡처로 대체되었습니다. 이러한 기능은 Windows 10 및 Windows 11 최적화되었습니다. 가능한 경우 새 코드가 DirectShow 대신 Media Foundation에서 MediaPlayer, IMFMediaEngine오디오/비디오 캡처를 사용하는 것이 좋습니다. 가능한 경우 레거시 API를 사용하는 기존 코드를 다시 작성하여 새 API를 사용하도록 제안합니다.]

이 항목에서는 DV 비디오를 캡처하는 USB(유니버설 직렬 버스) 비디오 디바이스용 애플리케이션을 작성하는 방법을 설명합니다.

표준 DV 형식의 데이터 속도는 초당 약 25메가비트(Mbps)입니다. USB가 처음 도입되었을 때 DV 비디오를 지원하기에 충분한 대역폭이 없었습니다. 그러나 USB 2.0은 DV 비디오에 충분한 최대 480Mbps를 지원할 수 있습니다. 2003년에 릴리스된 USB 비디오 디바이스 클래스(UVC) 사양은 USB DV 비디오 디바이스에 대한 페이로드 형식을 정의합니다. UVC 디바이스용 WDM(Windows 드라이버 모델) 클래스 드라이버가 Windows XP 서비스 팩 2에 도입되었습니다.

대부분의 경우 UVC 드라이버는 IEEE 1394 디바이스용 MSDV 드라이버와 동일한 프로그래밍 모델을 지원합니다. MSDV용으로 작성된 애플리케이션은 UVC 디바이스를 지원하기 위해 약간만 수정해야 합니다.

UVC 드라이버는 다음 영역의 MSDV 드라이버와 다르게 동작합니다.

사용 중인 드라이버를 확인하려면 IAMExtDevice::get_DevicePort 호출합니다. MSDV 드라이버는 DEV_PORT_1394 플래그를 반환하고 UVC 드라이버는 DEV_PORT_USB 플래그를 반환합니다.

디바이스 노드

USB 용어에서 엔드포인트는 데이터가 디바이스에 들어오거나 나가는 지점입니다. 엔드포인트에는 입력(디바이스에서 호스트로) 또는 출력(호스트에서 디바이스로)의 데이터 흐름 방향이 있습니다. 이러한 방향을 호스트에 상대적인 것으로 생각하는 데 도움이 될 수 있습니다. 입력이 호스트로 이동합니다. 출력은 호스트에서 제공됩니다. 다음 다이어그램에서는 두 엔드포인트를 보여 줍니다.

usb 엔드포인트

UVC 디바이스에서 디바이스의 기능은 논리적으로 단위 및 터미널이라는 구성 요소로 나뉩니다. 단위는 하나 이상의 데이터 스트림을 입력으로 수신하고 정확히 하나의 스트림을 출력으로 전달합니다. 터미널은 데이터 스트림의 시작점 또는 끝점입니다. USB 엔드포인트는 터미널에 해당하지만 방향은 반대로 바뀝니다. 입력 엔드포인트는 출력 터미널로 표시되고 그 반대의 경우도 마찬가지입니다. 다음 다이어그램은 터미널과 엔드포인트 간의 관계를 보여 줍니다.

uvc 단위 및 터미널

또한 모든 터미널이 USB 엔드포인트에 해당하는 것은 아닙니다. 엔드포인트라는 용어는 특히 USB 연결을 의미하며 디바이스는 비 USB 연결을 통해 데이터를 보내거나 받을 수 있습니다. 예를 들어 비디오 카메라는 입력 터미널이고 LCD 화면은 출력 터미널입니다.

KS 프록시 필터에서 단위 및 터미널은 필터 내부의 노드로 표시됩니다. 비 USB 디바이스에도 노드가 있을 수 있으므로 노드라는 용어는 단위 및 터미널이라는 용어보다 더 일반적입니다. 필터의 노드에 대한 정보를 얻으려면 IKsTopologyInfo 인터페이스에 대한 필터를 쿼리합니다. 노드 형식은 GUID로 식별됩니다. 선택기 노드는 둘 이상의 입력 간에 전환할 수 있는 노드입니다. 선택기 노드는 ISelector 인터페이스를 노출합니다.

다음 코드는 필터의 출력 핀이 지정된 형식의 노드에서 입력을 수신하는지 여부를 테스트합니다.

// Structure to hold topology information.
struct TopologyConnections
{
    KSTOPOLOGY_CONNECTION *connections; // Array of connections
    DWORD count;  // Number of elements in the array
};

/////////////////////////////////////////////////////////////////////
// Name: GetTopologyConnections
// Desc: Gets the topology information from a filter.
//
// pTopo:       Pointer to the filter's IKsTopologyInfo interface.
// connectInfo: Pointer to a TopologyConnections structure. The 
//              function fills in this structure.
//
// Note: If the function succeeds, call CoTaskMemFree to free the
//       pConnectInfo->connections array.
/////////////////////////////////////////////////////////////////////

HRESULT GetTopologyConnections(
    IKsTopologyInfo *pTopo, 
    TopologyConnections *pConnectInfo
    )
{
    DWORD count;
    HRESULT hr = pTopo->get_NumConnections(&count);
    if (FAILED(hr))
    {
        return hr;
    }

    pConnectInfo->count = count;
    pConnectInfo->connections = NULL;
    if (count > 0)
    {
        // Allocate an array for the connection information.
        SIZE_T cb = sizeof(KSTOPOLOGY_CONNECTION) * count;
        KSTOPOLOGY_CONNECTION *pConnections = 
            (KSTOPOLOGY_CONNECTION*) CoTaskMemAlloc(cb);
        if (pConnections == NULL)
        {
            return E_OUTOFMEMORY;
        }
        // Fill the array.
        for (DWORD ix = 0; ix < count; ix++)
        {
            hr = pTopo->get_ConnectionInfo(ix, &pConnections[ix]);
            if (FAILED(hr))
            {
                break;
            }
        }
        if (SUCCEEDED(hr))
        {
            pConnectInfo->connections = pConnections;
        }
        else
        {
           CoTaskMemFree(pConnections);
        }
    }
    return hr;
}

/////////////////////////////////////////////////////////////////////
// Name: IsNodeDownstreamFromNode
// Desc: Searches upstream from a node for a specified node type.
//
// pTopo:        Pointer to the filter's IKsTopologyInfo interface.
// connectInfo:  Contains toplogy information. To fill in this
//               structure, call GetTopologyConnections.
// nodeID:       ID of the starting node in the search.
// nodeType:     Type of node to find.
// pIsConnected: Receives true if connected, or false otherwise.
//
// Note: If the source node matches the type, this function returns 
//       true without searching upstream.
/////////////////////////////////////////////////////////////////////

HRESULT IsNodeDownstreamFromNode(
    IKsTopologyInfo *pTopo,
    const TopologyConnections& connectInfo, 
    DWORD nodeID,
    const GUID& nodeType,
    bool *pIsConnected
    )
{
    *pIsConnected = false;
    // Base case for recursion: check the source node.
    GUID type;
    HRESULT hr = pTopo->get_NodeType(nodeID, &type);
    if (FAILED(hr))
    {
        return hr;
    }
    if (type == nodeType)
    {
        *pIsConnected = true;
        return S_OK;
    }

    // If the source node is a selector, get the input node.
    CComPtr<ISelector> pSelector;
    hr = pTopo->CreateNodeInstance(nodeID, __uuidof(ISelector), 
        (void**)&pSelector);
    if (SUCCEEDED(hr))
    {
        DWORD sourceNodeID;
        hr = pSelector->get_SourceNodeId(&sourceNodeID);
        if (SUCCEEDED(hr))
        {
            // Recursive call with the selector's input node.
            return IsNodeDownstreamFromNode(pTopo, connectInfo, 
                       sourceNodeID, nodeType, pIsConnected);
        }
    }
    else if (hr == E_NOINTERFACE)
    {
        hr = S_OK;  // This node is not a selector. Not a failure.
    }
    else
    {
        return hr;
    }

    // Test all of the upstream connections on this pin. 
    for (DWORD ix = 0; ix < connectInfo.count; ix++)
    {
        if ((connectInfo.connections[ix].ToNode == nodeID) &&
            (connectInfo.connections[ix].FromNode != KSFILTER_NODE))
        {
            // FromNode is connected to the source node.
            DWORD fromNode = connectInfo.connections[ix].FromNode;

            // Recursive call with the upstream node.
            bool bIsConnected;
            hr = IsNodeDownstreamFromNode(pTopo, connectInfo, 
                fromNode, nodeType, &bIsConnected);
            if (FAILED(hr))
            {
                break;
            }
            if (bIsConnected)
            {
                *pIsConnected = true;
                break;
            }
        }
    }
    return hr;
}


/////////////////////////////////////////////////////////////////////
// Name: GetNodeUpstreamFromPin
// Desc: Finds the node connected to an output pin.
//
// connectInfo: Contains toplogy information. To fill in this
//              structure, call GetTopologyConnections.
// nPinIndex:   Index of the output pin.
// pNodeID:     Receives the ID of the connected node.
/////////////////////////////////////////////////////////////////////

HRESULT GetNodeUpstreamFromPin(
    const TopologyConnections& connectInfo, 
    UINT nPinIndex, 
    DWORD *pNodeID
    )
{
    bool bFound = false;
    for (DWORD ix = 0; ix < connectInfo.count; ix++)
    {
        if ((connectInfo.connections[ix].ToNode == KSFILTER_NODE) &&
            (connectInfo.connections[ix].ToNodePin == nPinIndex))
        {
            *pNodeID = connectInfo.connections[ix].FromNode;
            bFound = true;
            break;
        }
    }
    if (bFound)
    {
        return S_OK;
    }
    else
    {
        return E_FAIL;
    }
}


/////////////////////////////////////////////////////////////////////
// Name: IsPinDownstreamFromNode
// Desc: Tests whether an output pin gets data from a node of
//       a specified type.
// 
// pFilter:      Pointer to the filter's IBaseFilter interface.
// UINT:         Index of the output pin to test.
// nodeType:     Type of node to find.
// pIsConnected: Receives true if connected; false otherwise.
/////////////////////////////////////////////////////////////////////

HRESULT IsPinDownstreamFromNode(
    IBaseFilter *pFilter, 
    UINT nPinIndex, 
    const GUID& nodeType,
    bool *pIsConnected
    )
{
    CComQIPtr<IKsTopologyInfo> pTopo(pFilter);
    if (pTopo == NULL)
    {
        return E_NOINTERFACE;
    }

    // Get the topology connection information.
    TopologyConnections connectionInfo;
    HRESULT hr = GetTopologyConnections(pTopo, &connectionInfo);
    if (FAILED(hr))
    {
        return hr;
    }

    // Find the node upstream from this pin.
    DWORD nodeID;
    hr = GetNodeUpstreamFromPin(connectionInfo, nPinIndex, &nodeID);
    if (SUCCEEDED(hr))
    {
        bool isConnected;
        hr = IsNodeDownstreamFromNode(pTopo, connectionInfo, 
            nodeID, nodeType, &isConnected);
        if (SUCCEEDED(hr))
        {
            *pIsConnected = isConnected;
        }
    }
    CoTaskMemFree(connectionInfo.connections);
    return hr;
}

DirectShow의 디지털 비디오