控制外部设备

[与此页面关联的功能 DirectShow 是一项旧功能。 它已被 MediaPlayerIMFMediaEngine媒体基金会中的音频/视频捕获取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能使用 MediaPlayerIMFMediaEngineMedia Foundation 中的音频/视频捕获 ,而不是 DirectShow。 如果可能,Microsoft 建议重写使用旧 API 的现有代码以使用新 API。]

若要 (VTR) 设备控制录像带录制器,请使用 IAMExtTransport::p ut_Mode 方法。 使用 设备传输状态中列出的常量之一指定新状态。 例如,若要停止设备,请使用以下命令:

pTransport->put_Mode(ED_MODE_STOP); 

由于 VTR 是物理设备,因此在发出命令和命令完成之间通常会有一个延迟。 应用程序应创建第二个工作线程,等待命令完成。 命令完成后,线程可以更新用户界面。 使用关键部分来序列化状态更改。

当设备的传输状态发生更改时,某些 VDR 可以通知应用程序。 如果设备支持此功能,则工作线程可以等待通知。 但是,根据 1394 行业协会的“AV/C 磁带记录器/播放器子单元规范”,传输状态通知命令是可选的,这意味着设备不需要支持它。 如果设备不支持通知,则应定期轮询设备的当前状态。

本部分首先介绍通知机制,然后介绍设备轮询。

使用传输状态通知

传输状态通知的工作原理是让驱动程序在传输切换到新状态时发出事件信号。 在应用程序中,声明关键节、事件和线程句柄。 关键部分用于同步设备状态。 事件用于在应用程序退出时停止工作线程:

HANDLE hThread = 0;
HANDLE hThreadEnd = CreateEvent(NULL, TRUE, FALSE, NULL); 
if (hThreadEnd == NULL)
{
    // Handle error.
}
CRITICAL_SECTION csIssueCmd;
InitializeCriticalSection(&cdIssueCmd);

创建捕获筛选器的实例后,创建工作线程:

DWORD ThreadId;
hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &ThreadId);

在工作线程中,首先调用值为 ED_NOTIFY_HEVENT_GET 的 IAMExtTransport::GetStatus 方法。 此调用返回一个事件的句柄,该句柄将在操作完成时发出信号:

// Get the handle to the notification event.
HANDLE hEvent = NULL;
hr = pTransport->GetStatus(ED_NOTIFY_HEVENT_GET, (long*)&hNotify);

接下来,再次调用 GetState 并将值传递ED_MODE_CHANGE_NOTIFY:

LONG State;
hr = pTransport->GetStatus(ED_MODE_CHANGE_NOTIFY, &State);

如果设备支持通知,该方法将返回值E_PENDING。 (否则,必须轮询设备,如下一节中所述。) 假设设备支持通知,每当 VTR 传输状态更改时,都会发出事件信号。 此时,可以更新用户界面以反映新状态。 若要获取下一个通知,请重置事件句柄,并使用ED_MODE_CHANGE_NOTIFY再次调用 GetStatus

在工作线程退出之前,使用标志ED_NOTIFY_HEVENT_RELEASE和句柄地址调用 GetStatus 来释放事件句柄:

hr = pTransport->GetStatus(ED_NOTIFY_HEVENT_RELEASE, (long*)&hNotify)

以下代码显示了完整的线程过程。 假定函数 UpdateTransportState 是更新用户界面的应用程序函数。 请注意,线程等待两个事件:通知事件 (hNotify) 和线程终止事件 (hThreadEnd) 。 另请注意关键部分用于保护设备状态变量的位置。

DWORD WINAPI ThreadProc(void *pParam)
{
    HRESULT hr;
    HANDLE  EventHandles[2];
    HANDLE  hNotify = NULL;
    DWORD   WaitStatus;
    LONG    State;

    // Get the notification event handle. This event will be signaled when
    // the next state-change operation completes.   
    hr = pTransport->GetStatus(ED_NOTIFY_HEVENT_GET, (long*)&hNotify);

    while (hThread && hNotify && hThreadEnd) 
    {
        EnterCriticalSection(&csIssueCmd);
        // Ask the device to notify us when the state changes.
        hr = pTransport->GetStatus(ED_MODE_CHANGE_NOTIFY, &State);
        LeaveCriticalSection(&csIssueCmd); 

        if(hr == E_PENDING)  // The device supports notification.
        {
            // Wait for the notification.
            EventHandles[0] = hNotify;
            EventHandles[1] = hThreadEnd;
            WaitStatus = WaitForMultipleObjects(2, EventHandles, FALSE, INFINITE);
            if(WAIT_OBJECT_0 == WaitStatus) 
            {
                // We got notified. Query for the new state.
                EnterCriticalSection(&csIssueCmd);  
                hr = m_pTransport->get_Mode(State);
                UpdateTransportState(State);  // Update the UI.
                LeaveCriticalSection(&m_csIssueCmd);
                ResetEvent(hNotify);
            } 
            else {
                break;  // End this thread.
            }
        } 
        else {          
            // The device does not support notification.
            PollDevice();        
        } 
    } // while

    // Cancel notification. 
    hr = pTransport->GetStatus(ED_NOTIFY_HEVENT_RELEASE, (long*)&hNotify);
    return 1; 
}

向设备发出命令时,也可以使用“关键”部分,如下所示:

// Issue the "stop" command.
EnterCriticalSection(&csIssueCmd); 
if (SUCCEEDED(hr = pTransport->put_Mode(ED_MODE_STOP)))
{
    UpdateTransportState(ED_MODE_STOP);
}
LeaveCriticalSection(&csIssueCmd); 

在应用程序退出之前,通过设置线程端事件来停止辅助线程:

if (hThread) 
{
    // Signaling this event will cause the thread to end.    
    if (SetEvent(hThreadEnd))
    {
        // Wait for it to end.
        WaitForSingleObjectEx(hThread, INFINITE, FALSE);
    }
}
CloseHandle(hThreadEnd);
CloseHandle(hThread);

轮询传输状态

如果使用 ED_MODE_CHANGE_NOTIFY 标志调用 IAMExtTransport::GetStatus ,并且返回值未E_PENDING,则表示设备不支持通知。 在这种情况下,必须轮询设备以确定其状态。 轮询仅意味着定期调用get_Mode以检查传输状态。 仍应使用辅助线程和关键部分,如前所述。 线程定期查询设备的状态。 以下示例演示了实现线程的一种方法:

DWORD WINAPI ThreadProc(void *pParam)
{
    HRESULT hr;
    LONG State;
    DWORD WaitStatus;

    while (hThread && hThreadEnd) 
    {
        EnterCriticalSection(&csIssueCmd);  
        State = 0;
        hr = pTransport->get_Mode(&State);
        LeaveCriticalSection(&csIssueCmd); 
        UpdateTransportState(State);

        // Wait for a while, or until the thread ends. 
        WaitStatus = WaitForSingleObjectEx(hThreadEnd, 200, FALSE); 
        if (WaitStatus == WAIT_OBJECT_0)
        {
            break; // Exit thread now. 
        }
        // Otherwise, the wait timed out. Time to poll again.
    }
    return 1;
}

控制 DV 摄像机