Controlling an External Device

[The feature associated with this page, DirectShow, is a legacy feature. It has been superseded by MediaPlayer, IMFMediaEngine, and Audio/Video Capture in Media Foundation. Those features have been optimized for Windows 10 and Windows 11. Microsoft strongly recommends that new code use MediaPlayer, IMFMediaEngine and Audio/Video Capture in Media Foundation instead of DirectShow, when possible. Microsoft suggests that existing code that uses the legacy APIs be rewritten to use the new APIs if possible.]

To control a video tape recorder (VTR) device, use the IAMExtTransport::put_Mode method. Specify the new state by using one of the constants listed in the Device Transport State. For example, to stop the device, use the following:

pTransport->put_Mode(ED_MODE_STOP); 

Because the VTR is a physical device, there is typically a lag between issuing the command and when the command completes. Your application should create a second worker thread that waits for the command to finish. When the command finishes, the thread can update the user interface. Use a critical section to serialize the state change.

Some VTRs can notify the application when the device's transport state has changed. If the device supports this feature, the worker thread can wait for the notification. According to the 1394 Trade Association's "AV/C Tape Recorder/Player Subunit Specification", however, the transport-state notification command is optional, meaning devices are not required to support it. If a device does not support notification, you should poll the device at periodic intervals for its current state.

This section first describes the notification mechanism, and then describes device polling.

Using Transport State Notification

Transport state notification works by having the driver signal an event when the transport switches to a new state. In your application, declare a critical section, an event, and a thread handle. The critical section is used to synchronize the device state. The event is used to halt the worker thread when the application exits:

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

After you create an instance of the capture filter, create the worker thread:

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

In the worker thread, start by calling the IAMExtTransport::GetStatus method with the value ED_NOTIFY_HEVENT_GET. This call returns a handle to an event that will be signaled when an operation completes:

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

Next, call GetState again and pass the value ED_MODE_CHANGE_NOTIFY:

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

If the device supports notification, the method returns the value E_PENDING. (Otherwise, you must poll device, as described in the next section.) Assuming the device supports notification, the event will be signaled whenever the state of the VTR transport changes. At this point, you can update the user interface to reflect the new state. To get the next notification, reset the event handle and call GetStatus with ED_MODE_CHANGE_NOTIFY again.

Before the worker thread exits, release the event handle by calling GetStatus with the flag ED_NOTIFY_HEVENT_RELEASE and the address of the handle:

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

The following code shows the complete thread procedure. The function UpdateTransportState is assumed to be an application function that updates the user interface. Note that the thread waits for two events: the notification event (hNotify) and the thread-termination event (hThreadEnd). Also note where the critical section is used to protect the device state variable.

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

Also use the critical section when you issue commands to the device, as follows:

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

Before the application exits, halt the secondary thread by setting the thread-end event:

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);

Polling the Transport State

If you call IAMExtTransport::GetStatus with the ED_MODE_CHANGE_NOTIFY flag and the return value is not E_PENDING, it means the device does not support notification. In that case, you must poll the device to determine its state. Polling simply means calling get_Mode at regular intervals to check the transport state. You should still use a secondary thread and a critical section, as described earlier. The thread queries the device for its state at a regular interval. The following example shows one way to implement the thread:

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

Controlling a DV Camcorder