Arbeiten mit USB-DV-Videogeräten

[Das dieser Seite zugeordnete Feature DirectShow ist ein Legacyfeature. Es wurde von MediaPlayer, IMFMediaEngine und Audio/Video Capture in Media Foundation abgelöst. Diese Features wurden für Windows 10 und Windows 11 optimiert. Microsoft empfiehlt dringend, dass neuer Code mediaPlayer, IMFMediaEngine und Audio/Video Capture in Media Foundation anstelle von DirectShow verwendet, wenn möglich. Microsoft schlägt vor, dass vorhandener Code, der die Legacy-APIs verwendet, so umgeschrieben wird, dass nach Möglichkeit die neuen APIs verwendet werden.]

In diesem Thema wird beschrieben, wie Sie Anwendungen für USB-Videogeräte (Universal Serial Bus) schreiben, die DV-Video aufzeichnen.

Das Standard-DV-Format hat eine Datenrate von etwa 25 Megabit pro Sekunde (MBit/s). Als USB zum ersten Mal eingeführt wurde, verfügte es nicht über genügend Bandbreite, um DV-Videos zu unterstützen. USB 2.0 kann jedoch bis zu 480 MBit/s unterstützen, was für DV-Videos mehr als ausreichend ist. Die 2003 veröffentlichte UVC-Spezifikation (USB Video Device Class) definiert das Nutzlastformat für USB-DV-Videogeräte. Ein WDM-Klassentreiber (Windows Driver Model) für UVC-Geräte wurde in Windows XP Service Pack 2 eingeführt.

In den meisten Fällen unterstützt der UVC-Treiber dasselbe Programmiermodell wie der MSDV-Treiber für IEEE 1394-Geräte. Für MSDV geschriebene Anwendungen sollten nur geringfügige Änderungen zur Unterstützung von UVC-Geräten erfordern.

Der UVC-Treiber verhält sich in den folgenden Bereichen anders als der MSDV-Treiber:

Rufen Sie IAMExtDevice::get_DevicePort auf, um zu bestimmen, welcher Treiber verwendet wird. Der MSDV-Treiber gibt das flag DEV_PORT_1394 zurück, und der UVC-Treiber gibt das flag DEV_PORT_USB zurück.

Geräteknoten

In der USB-Terminologie sind Endpunkte die Punkte, an denen Daten in das Gerät gelangen oder dieses verlassen. Ein Endpunkt verfügt über eine Richtung des Datenflusses, entweder Eingabe (vom Gerät zum Host) oder Ausgabe (von Host zu Gerät). Es kann hilfreich sein, sich diese Richtungen als relativ zum Wirt zu vorstellen. Die Eingabe geht an den Host; die Ausgabe stammt vom Host. Das folgende Diagramm veranschaulicht die beiden Endpunkte.

USB-Endpunkte

In einem UVC-Gerät werden die Funktionen des Geräts logisch in Komponenten unterteilt, die als Einheiten und Terminals bezeichnet werden. Eine Einheit empfängt einen oder mehrere Datenströme als Eingabe und liefert genau einen Stream als Ausgabe. Ein Terminal ist der Ausgangspunkt oder Endpunkt für einen Datenstrom. USB-Endpunkte entsprechen Terminals, aber die Richtungen sind umgekehrt: Ein Eingabeendpunkt wird durch ein Ausgabeterminal dargestellt und umgekehrt. Das folgende Diagramm zeigt die Beziehung zwischen Terminals und Endpunkten.

uvc-Einheiten und -Terminals

Außerdem entspricht nicht jedes Terminal einem USB-Endpunkt. Der Begriff Endpunkt bezieht sich speziell auf USB-Verbindungen, und ein Gerät kann Daten über Nicht-USB-Verbindungen senden oder empfangen. Beispielsweise ist eine Videokamera ein Eingangsterminal, und ein LCD-Bildschirm ist ein Ausgabeterminal.

Im KS-Proxyfilter werden Einheiten und Terminals als Knoten innerhalb des Filters dargestellt. Der Begriff Knoten ist allgemeiner als die Begriffe Einheit und Terminal, da Nicht-USB-Geräte auch Knoten haben können. Um Informationen zu den Knoten in einem Filter abzurufen, fragen Sie den Filter für die IKsTopologyInfo-Schnittstelle ab. Knotentypen werden durch GUIDs identifiziert. Auswahlknoten sind Knoten, die zwischen zwei oder mehr Eingaben wechseln können. Selektorknoten machen die ISelector-Schnittstelle verfügbar.

Der folgende Code testet, ob ein Ausgabepin an einem Filter Eingaben von einem Knoten eines bestimmten Typs empfängt.

// 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;
}

Digitales Video in DirectShow