Microsoft DirectX 8.0 |
This article describes how to control a digital video (DV) camcorder using Microsoft® DirectShow®. It contains the following sections:
For a general overview of DV in DirectShow, see Digital Video in DirectShow.
The WDM Video Capture filter exposes three interfaces for controlling a camcorder.
IAMExtDevice | The base interface for external device control. |
IAMExtTransport | Controls the VCR functions. |
IAMTimeCodeReader | Reads timecode from the device. |
After you select a capture device and create an instance of the capture filter, query the filter for these interfaces. The following example declares a custom structure that holds the interface pointers, along with Boolean values that specify the availability of each interface:
struct _MyDevCap { IAMExtDevice *pDevice; IAMExtTransport *pTransport; IAMTimecodeReader *pTimecode; BOOL bHasDevice; BOOL bHasTransport; BOOL bHasTimecode; } MyDevCap; HRESULT hr; IBaseFilter *pDVCam; // Pointer to the capture filter. // Create an instance of the capture filter (not shown). hr = pDVCam->QueryInterface(IID_IAMExtDevice, (void **)&MyDevCap.pDevice); MyDevCap.bHasDevice = (SUCCEEDED(hr)); hr = pDVCam->QueryInterface(IID_IAMExtTransport, (void **)&MyDevCap.pTransport); MyDevCap.bHasTransport = (SUCCEEDED(hr)); hr = pDVCam->QueryInterface(IID_IAMTimecodeReader, (void **)&MyDevCap.pTimecode); MyDevCap.bHasTimecode = (SUCCEEDED(hr));
Note To use these interfaces with Msdv.sys, the DV camcorder driver, include the header file XPrtDefs.h in your project. You can obtain this file at msdn.microsoft.com/library/techart/dvsupport.htm.
The device control interfaces enable you to query the device for its status and capabilities. This section describes a few of the more useful items you can retrieve. For more information, refer to the interface references.
The most important piece of information about a capture device is its typewhether it has VCR functions, or is strictly a camera. To retrieve the device type, call the IAMExtDevice::GetCapability method with the value ED_DEVCAP_DEVICE_TYPE. If the method returns the value ED_DEVTYPE_VCR, the device is a VCR and has functions such as pause, stop, fast-forward, and rewind. The following code example shows how to query the device type:
if (MyDevCap.bHasDevice) { LONG lDeviceType = 0; MyDevCap.pDevice->GetCapability(ED_DEVCAP_DEVICE_TYPE, &lDeviceType, 0); if (lDeviceType == ED_DEVTYPE_VCR) { // Device is a VCR. Enable all VCR functions. } else { // Device is a camera. // Enable record and record-pause; disable other functions. } }
Note Typically, a camcorder can switch between VCR and camera functions. It returns the corresponding device type. If the camcorder goes offline, you should query it again the next time it becomes available. The filter graph manager posts an EC_DEVICE_LOST event when the device is removed. For more information, see How to Write a Capture Application.
To retrieve the current state of the device, such as play, pause, or stop, call the IAMExtTransport::get_Mode method. The method retrieves a constant that indicates the device state:
Value | Device State |
---|---|
ED_MODE_PLAY | Play |
ED_MODE_STOP | Stop |
ED_MODE_FREEZE | Pause |
ED_MODE_FF | Fast-forward |
ED_MODE_REW | Rewind |
ED_MODE_RECORD | Record |
ED_MODE_RECORD_FREEZE | Record-pause |
While a DV tape is playing or is in record-pause mode, you can retrieve the SMPTE timecode or the absolute track number. To do this, call the IAMTimecodeReader::GetTimecode method. This method takes a pointer to a TIMECODE_SAMPLE structure, which describes the timecode. Before calling the method, initialize the dwFlags member of the structure. Use the value ED_DEVCAP_TIMECODE_READ to retrieve the timecode or the value ED_DEVCAP_ATN_READ to retrieve the absolute track number.
The timecode member of the TIMECODE_SAMPLE structure is a TIMECODE structure. When the method returns, the dwFrames member of the TIMECODE structure contains the timecode or track number. For timecode, the hours, minutes, seconds, and frames are packed into a DWORD as binary coded decimal (BCD) values, with the format hhmmssff. Use bitmasks to extract the individual values.
The following example retrieves the timecode and track number. The functions DisplayTimecode and DisplayTrackNum are assumed to be application-defined functions that display the information.
if (MyDevCap.bHasTimecode) { TIMECODE_SAMPLE TimecodeSample; TimecodeSample.timecode.dwFrames = 0; char szBuf[32]; TimecodeSample.dwFlags = ED_DEVCAP_TIMECODE_READ; if (hr = MyDevCap.pTimecode->GetTimecode(&TimecodeSample), SUCCEEDED(hr)) { wsprintf(szBuf, "TIMECODE %.2x : %.2x : %.2x : %.2x", (TimecodeSample.timecode.dwFrames & 0xFF000000) >> 24, (TimecodeSample.timecode.dwFrames & 0x00FF0000) >> 16, (TimecodeSample.timecode.dwFrames & 0x0000FF00) >> 8, (TimecodeSample.timecode.dwFrames & 0x000000FF)); DisplayTimecode(szBuf); } TimecodeSample.dwFlags = ED_DEVCAP_ATN_READ; if (hr = MyDevCap.pTimecode->GetTimecode(&TimecodeSample), SUCCEEDED(hr)) { wsprintf(szBuf, "%d", TimecodeSample.timecode.dwFrames); DisplayTrackNum(szBuf); } }
To control the device, call the IAMExtTransport::put_Mode method. Specify the new state by using one of the constants listed in the previous section. For example, to stop the device, use the following:
MyDevCap.pTransport->put_Mode(ED_MODE_STOP);
Because the camcorder is a physical device, however, there might be a lag between when the command is issued and when it completes. Your application should create a secondary 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 camcorders can notify the application when the state of the device changes. If the device supports this feature, the secondary thread can wait for the notification. If not, the application must poll the device at periodic intervals.
First declare a critical section variable, an event, and a thread handle. The critical section is used to synchronize the device state. The event is used to end the secondary thread when the application exits:
CRITICAL_SECTION csIssueCmd; HANDLE hThreadEndEvent = CreateEvent(NULL, TRUE, FALSE, NULL); HANDLE hThread = 0;
After you create an instance of the capture filter, create the secondary thread:
DWORD ThreadId; hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &ThreadId);
Within the secondary thread, make two calls to the IAMExtTransport::GetStatus method. In the first call, pass the value ED_NOTIFY_HEVENT_GET. This call retrieves a handle to an event that will be signaled when an operation completes:
HANDLE hEvent = NULL; hr = MyDevCap.pTransport->GetStatus(ED_NOTIFY_HEVENT_GET, (long *)&hEvent);
In the second call, pass the value ED_MODE_CHANGE_NOTIFY, along with a pointer to a variable that will receive the device state:
LONG State; hr = MyDevCap.pTransport->GetStatus(ED_MODE_CHANGE_NOTIFY, &State);
If the device supports notification, the return value from this call is E_PENDING. Now wait for the event to be signaled. When the next operation completes, the event is signaled and the value of State specifies the new device state. At this point, you can update the user interface to reflect the new state.
The following code shows the complete thread procedure. The function UpdateTransportState is assumed to be an application-defined function that updates the user interface. Note that the thread waits for two events: the notification event (hEvent) and the thread-termination event (hThreadEndEvent). 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 hEvent = NULL; DWORD WaitStatus; LONG State; // Get an event, which will be signaled when the next operation completes. hr = MyDevCap.pTransport->GetStatus(ED_NOTIFY_HEVENT_GET, (long *) &hEvent); while (hThread && hEvent && hThreadEndEvent) { EnterCriticalSection(&csIssueCmd); State = 0; hr = MyDevCap.pTransport->GetStatus(ED_MODE_CHANGE_NOTIFY, &State); LeaveCriticalSection(&csIssueCmd); if(hr == E_PENDING) { // Device supports notification. EventHandles[0] = hEvent; EventHandles[1] = hThreadEndEvent; WaitStatus = WaitForMultipleObjects(2, EventHandles, FALSE, INFINITE); if(WAIT_OBJECT_0 == WaitStatus) { UpdateTransportState(State); } else { break; // End this thread. } } else { break; // End this thread. (Device does not support notification.) } } // while // Cancel notification. hr = MyDevCap.pTransport->GetStatus(ED_NOTIFY_HEVENT_RELEASE, (long *) &hEvent); return 1; }
Also use the critical section when you issue commands to the device, as follows:
// Issue the "stop" command. EnterCriticalSection(&csIssueCmd); if(hr = MyDevCap.pTransport->put_Mode(ED_MODE_STOP), SUCCEEDED(hr)) UpdateTransportState(ED_MODE_STOP); LeaveCriticalSection(&csIssueCmd);
Before the application exits, end the secondary thread:
if (hThread) { // Signaling this event will cause the thread to end. if (SetEvent(ThreadEndEvent)) { // Wait for it to end. WaitForSingleObjectEx(hThread, INFINITE, FALSE); } CloseHandle(hThreadEndEvent); CloseHandle(hThread);
If the device does not support notification, then calling IAMExtTransport::GetStatus with the value ED_MODE_CHANGE_NOTIFY will return a value other than E_PENDING. In that case, you must poll the device to determine its 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 && hThreadEndEvent) { EnterCriticalSection(&csIssueCmd); State = 0; hr = MyDevCap.pTransport->get_Mode(&State); LeaveCriticalSection(&csIssueCmd); UpdateTransportState(State); // Wait for a while, or until the thread ends. WaitStatus = WaitForSingleObjectEx(hThreadEndEvent, 200, FALSE); if (WaitStatus == WAIT_OBJECT_0) break; // Exit thread now. // Otherwise, the wait timed out. Time to poll again. } return 1; }
Use the device control interfaces to transmit video from the DV tape to a computer file, or from a file to the tape:
The following diagram shows the filter graph for transmitting from file to tape.
Digital Video Camcorder Support in Windows 98 and Windows 2000 (www.microsoft.com/hwdev/vidcap/DVCam.htm) is a white paper that discusses DV camcorder control.