Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

A Video for Windows ActiveX Control


Jun99: Programmer's Toolchest

Ofer is the technical director of Quality By Vision, and is the author of CGI Programming with Visual Basic 5 (McGraw-Hill). He can be reached at [email protected].


Support for Visual C++ 6


Video for Windows (VFW), introduced by Microsoft during the 16-bit Windows era, was designed to let applications interact with video-capture cards (also known as "video grabbers" or "frame grabbers") in a consistent manner, allowing applications to display and record video from any device.

I wrote an ActiveX control, oVFW, that encapsulates VFW's features, allowing applications -- including Visual Basic apps -- to interact with a video-capture card. In this article, I'll describe oVFW, and present a sample Visual Basic application that uses this control.

Video for Windows

Video for Windows exposes a set of API functions that lets an application create a VFW window, attach a capture driver to it, capture a frame (or a video stream), and preview live video. The VFW SDK comes standard with most Microsoft development environments, such as Visual C++.

Most of the VFW API functions are actually simple macro wrappers around the Win32 SendMessage function with prefilled parameters. For example, the VFW API function capGrabFrameNoStop is actually a macro consisting of SendMessage called with WM_CAP_GRAB_FRAME_NOSTOP as a parameter.

To interact with VFW, applications must first create a window that VFW can control. This window can be a child of any of the application's windows, so it appears to be fully controlled by the application itself. In reality, VFW handles all of the WM messages that the window receives. To create the VFW window, the capCreateCaptureWindow API function is called. The capDriverConnect API function causes VFW to bind a video-capture driver to a particular VFW window.

Once a driver is connected to a VFW window, the application can make the window show live video, question it for video-driver support parameters, or have it capture a frame.

Live Video

Most video-capture cards offer two modes for displaying live video -- preview and overlay. Overlay-enabled video-capture cards interface directly with the display adapter, transferring video information directly to it via the PCI bus (typically using a fast DMA transfer) or through a proprietary bus between the video-capture card and video-display adapter. Overlay mode uses an extremely small amount of resources from the host machine, because the DMA, not the CPU, is transferring the video frames. Typically, the capture card becomes the master of the PCI bus for a short duration and uses a DMA channel to transfer a burst of video information directly to the video-display adapter's onboard memory.

In preview mode, VFW continually requests new frames from the driver and paints those frames itself using BitBlt or StretchBlt. In this case, the CPU requests the data, the capture card transfers the data to the system's regular memory, after which the CPU must transfer it to video memory again using BitBlt. Due to the amount of data transferred by the CPU, preview mode provides much slower frame rates. The frame rate drops even more with frame sizes larger than CIF (320×240).

Users typically prefer overlay mode, because of its low system overhead and high frame rates. However, overlay mode usually causes some slowdown on part of the system bus (slowing down other transfers because the PCI bus is busy with video information), and it is not always supported by the combination of video-capture cards, PCI bus chipsets, and video-display adapters (especially those with nonlinear memory). When overlay mode is not supported, preview mode is used instead.

Creating the oVFW Control

I created the oVFW control to give all types of applications, particularly Visual Basic applications, simple and consistent access to VFW. The control gives applications access to VFW driver information, overlay and preview live video, and single frame capture. I used the ActiveX Template Library (ATL) to create the control.

ATL is modeled after the C++ Standard Template Library (STL), and provides an application with full support for COM via easy to use template base classes. ATL requires little or no dependency on external DLLs or libraries, keeping a COM application very simple.

The ATL framework does not interfere with the actual object-oriented design of an application. A C++ class can contain any number of interfaces via inheritance. When creating a simple ActiveX control, ATL has little to say about what kind of user-interface the control will have. ATL is useful for creating all types of COM components, some with user-interface (ActiveX controls) and some without (Automation objects).

I used Microsoft Developer Studio's ATL COM application wizard to construct a new ATL workspace. The wizard was set to create an ATL DLL, which merges the proxy/stub code into the DLL itself. If the proxy/stub code were not merged into the DLL, ATL.DLL would have been required to use oVFW.

Next, I created a new ATL control, which served as the basis for the oVFW control. I then added the properties and methods for the control, as well as a simple automation object, which is used for querying VFW drivers for their capabilities.

The control supports both connection points and error handling. Support for error handling allows the control to emit errors to hosts that support COM error handling (Visual Basic apps, for instance). Support for connection points allows the control to trigger the enumeration event, which is described later.

Enumerating VFW Drivers

Since VFW limits a system to one window per driver, users must install more than one video-capture card to view several simultaneous live video feeds. Because video-capture cards are becoming increasingly popular and inexpensive, having multiple video-capture cards in a single machine is feasible. My own machine, for example, sports a TV card for viewing cable television while I program (that's my story and I'm sticking to it), and a second card for video conferencing and other capture applications. In systems with specialized applications, multiple video-capture cards can be used to enhance the application and provide additional functionality (a security application that would allow the operator to view several live images inside a bank, for instance).

VFW supports up to 10 different drivers running on a particular machine, and assigns an index to each. For an application to select the appropriate available driver, the oVFW control gives the hosting application the ability to enumerate the available VFW drivers.

To enumerate VFW drivers, the control has a function called DriversEnum, which attempts to attach to each driver. DriversEnum first creates a specialized VFW window using capCreateCaptureWindow. Then capDriverConnect is called for each of the driver indices. If capDriverConnect succeeds, the driver's properties are retrieved. If a driver is currently in use elsewhere in the system, the control will not detect it and will not be able to retrieve that driver's properties.

Once oVFW retrieves the driver information, it needs to expose it to the hosting application. One way to do this would be to store the information in a COM collection, and expose the collection to the application. However, I chose not to use a COM collection for two reasons:

  • I wanted the index of the collection to correspond to the index of the driver. Because the active VFW driver indices might not be sequential, the collection index would not always equal the driver index.
  • Using a COM collection would require caching the driver-information object. I wanted the hosting application to be able to control what information it wanted to retrieve and cache.

Rather than use a COM collection, DriversEnum uses COM connection points (events) to inform the hosting application which drivers are available and what the properties of those drivers are. The hosting application should supply at least one event handler for each event (otherwise, the event simply dissipates when it is fired). When DriversEnum successfully connects to a driver, it sets up a driver-information object and triggers the enumeration event. When the event handler returns, DriversEnum detaches from the driver and tries to attach to the next driver.

Listing One shows the event interface declared in the project's IDL file so that oVFW can support events. Listing Two shows how oVFW informs the world that it will trigger the interface, not receive it.

Figure 1 shows the Developer's Studio ATL proxy generator used to create an ATL base class for CoVFW. The base classes responsible for allowing CoVFW to trigger events are IProvideClassInfo2Impl<>, IConnectionPointContainerImpl<CoVFW>, and public Cproxy_oVFWEvents<CoVFW>, all in Listing Three.

The DIID__oVFWEvents constant must be declared next, as in Listing Four. This constant defines the unique class identification for the event interface. Also, the connection point entry must be declared; see Listing Five.

The output for the ATL proxy generator is an automatically created CProxy_ oVFWEvents class which provides a trigger function for the class's events (Fire_ DriversEnum). The full listing for the EnumDrivers function is available electronically; see "Resource Center," page 5.

The Current VFW Driver

Driver information can be accessed using the CoVFWDriverInfo class. This class is an additional ATL class that supports error information. The class retains local index and hWndC variables that let the class maintain the identity of the VFW driver to which it is attached. The oVFW control keeps one copy of the current driver, constructed in CoVFW's constructor (Listing Six) throughout its lifespan. This COM-enabled copy of the driver information object can be safely passed to the hosting application via a call to the get_CurDriver; the implementation is available electronically.

The ATL-provided CComObject template class provides COM access and reference counting, enabling outside objects to access these COM objects. AddRef is used to increment the reference counting and Release decrements it (and deletes the object if no other references to the object exist). The CoVFW destructor releases the object by performing a Release call; see Listing Seven.

Note that the pDriverInfo object is not deleted by the CoVFW destructor, because it may still have outside references in the hosting application, even after the actual control is released. Deleting the object would probably cause the hosting application to throw a protection fault the next time it attempts to access the object. Instead, the window handle is set to NULL, causing the driver to detect the problem and return an error to the host (Listing Eight).

Viewing Live Video

The ShowLiveVideo method turns live video on or off. When called, ShowLiveVideo creates the VFW child window and attaches it to the selected driver index. The hosting application can select a particular VFW driver index (set during the enumeration process) by setting the control property iVideo. In a typical single video-capture-card environment, this parameter will be zero.

The AttachVFW function (available electronically) determines the size of the client rectangle for the control, and calls the VFW API function capCreateCaptureWindow, which creates a VFW window as a child of the oVFW control, causing the entire visible space of the control to contain the video window, rather than empty space. The subsequent call to capDriverConnect lets the VFW window know that it should be attached to a particular VFW driver. If this function call fails, the driver is currently unavailable.

After attaching to the appropriate VFW driver, the StartLiveVideo function (available electronically) determines if the application wants an overlay window. If so, it determines if the attached driver is capable of supporting overlay mode. If not, the function simply turns off the overlay flag. This flag can be modified or inspected by the hosting application to determine if overlay mode is being used for the live-video display.

StartLiveVideo calls capPreviewScale, which stretches the live video within the control's client area. Next, the function determines if it should use overlay mode. If so, capOverlay is called to initiate live video. If preview mode is being used, the preview frame rate is calculated (using the frames per second requested in the previewRate control property) and committed using capPreviewRate. The capPreview function is used to initiate the preview mode. At this point, live video is being displayed on screen.

The StopLiveVideo function (available electronically) turns off both overlay and preview modes, and calls DetachVFW (available electronically), which detaches from the VFW driver. This function must also be called in the control's destructor to prevent a situation where live video is left open while the VFW hosting window has been destroyed. If this were to occur, subsequent attachment attempts to the selected VFW driver will be unsuccessful, and the video driver will remain inactive until the system restarts.

Some video-card drivers, such as ATI TV's drivers, have problems determining whether the video window has moved. This is because VFW does not monitor window movement, so the driver itself has to try and detect movement of the window containing the live video. Since the parent window, not the live-video window, has actually moved, some drivers may not realize that it has to move the overlay window too. This can result in live video playing where it shouldn't or not playing where it should.

One crude fix for this problem is to have the hosting application send WM _MOVE messages to the video window whenever it moves, subsequently causing the driver to pick up the message and place the live video in its correct position.

Unfortunately, this is ineffective in Visual Basic apps (no move event is supplied to Visual Basic forms) where subclassing is difficult and expensive, especially for just supplying WM_MOVE messages. Because the problem only occurs in overlay mode, using preview mode is an effective but crude solution. In preview mode, each frame is sent to its correct screen location using GDI functions instead of a DMA transfer.

Capturing Frames

The control implements two different methods of capturing single frames -- capturing frame information to the clipboard and capturing to a bitmap file. Visual Basic applications can easily access clipboard data using Listing Nine.

To capture to the clipboard, the CaptureEditCopy method first determines if the driver is attached (that is, if live video is presently being shown). If so, capGrabFrameNoStop is used to capture the image from the driver without stopping the live video. capEditCopy is then used to save the captured image onto the clipboard. capEditCopy can be used without first calling capGrabFrameNoStop; however, this stops the live video on some drivers.

If the control finds that no driver is attached, it will quickly attach a driver, capture a frame, and detach from the driver. The API function capGrabFrame is the functional equivalent of capGrabFrameNoStop, except that it definitely stops live video (both preview and overlay) when it is called.

Capturing to a bitmap file is similar to capturing to the clipboard, except capFileSaveDIB is used instead of cap-EditCopy. This is done by the CaptureToBitmapFile method (available electronically).

Sample Application

A Visual Basic application (available electronically) exercises all of the features that the control has to offer. It provides live video depending on the overlay and video index parameters. It determines the capabilities of the current driver, and enables or disables some of the feature buttons accordingly. Figure 2 provides a view of the hosting Visual Basic control.

The ActiveX control can also be used from Microsoft Internet Explorer (IE), so that you can view live video on a web page. The most prominent difference when dealing with IE as a hosting environment is its stricter security. Internet Explorer wants to use controls that are "script safe" and "initialization safe." If controls are not marked as such, an annoying message will accompany each web page that incorporates the control.

To fix this, you need to make a simple addition to the control's registry settings. ATL inserts registry settings by having them placed in special RGS files that are automatically added to the project's resources. The contents of these RGS files are merged into the registry when the control is registered using a setup routine or regsvr32. The ATL wizard creates the basic RGS file, which can be subsequently modified as needed. This makes it very simple to modify the entries placed in the registry by the control during the registration process.

To have the control marked as script and initialization safe, you need to modify the RGS file, as in Listing Ten. The first long numerical key in this example identifies oVFW's unique COM class identifier. The other two keys identify the COM class identifier for the supported categories: script safe and initialization safe.

Listing Eleven is the HTML code that lets the control be shown on an Internet Explorer web page, and the actual resulting web page in Figure 3.

The Future of VFW

Microsoft considers VFW a dead dog, and has not changed its fundamental structure since Windows 3.1. Windows NT contains the first 32-bit implementation of VFW, which does not seem to improve its performance in any significant manner (in fact, preview mode is slower).

Microsoft's latest offering for this arena is DirectShow, a complex, COM-based, multilayered interface that allows enhanced access to TV tuners, filters, cable and satellite decoders, and more. DirectShow, which is included with Windows 98, is a stunning contrast to VFW. The requirements to write a minimal driver are quite complex. Additionally, most driver writers are required to write several layers. Microsoft also treats DirectX as a moving target, changing the specifications frequently. Luckily, DirectShow is backwards-compatible and will support VFW applications as well.

Most of the fundamental flaws that were found in VFW have been fixed in DirectShow, including access to tuner information. DirectShow provides clear information about window movement and live-video regions. However, due to the complex framework of COM-object layers involved in DirectShow, it will not provide a significant performance improvement over Video for Windows. Without an improvement in performance, preview mode will not be able to support full-motion video, which makes the implementation of things such as real-time filters for live video simply impossible.

DDJ

Listing One

[
    hidden,
    uuid(54526101-F0CA-11d1-969F-002018631632)
]
dispinterface _oVFWEvents
{
    properties:
    methods:
    [id(1)] void DriversEnum([in]long index, [in]IoVFWDriverInfo* driver);
};

Back to Article

Listing Two

coclass oVFW
{
    [default] interface IoVFW;
    [default, source] dispinterface _oVFWEvents;
};

Back to Article

Listing Three

class ATL_NO_VTABLE CoVFW : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CoVFW, &CLSID_oVFW>,
  public CComControl<CoVFW>,
  public CStockPropImpl<CoVFW, IoVFW, &IID_IoVFW, &LIBID_oVFW>,
  public IProvideClassInfo2Impl<&CLSID_oVFW, &DIID__oVFWEvents, &LIBID_oVFW>,
  public IPersistStreamInitImpl<CoVFW>,
  public IPersistStorageImpl<CoVFW>,
  public IQuickActivateImpl<CoVFW>,
  public IOleControlImpl<CoVFW>,
  public IOleObjectImpl<CoVFW>,
  public IOleInPlaceActiveObjectImpl<CoVFW>,
  public IViewObjectExImpl<CoVFW>,
  public IOleInPlaceObjectWindowlessImpl<CoVFW>,
  public IDataObjectImpl<CoVFW>,
  public ISupportErrorInfo,
  public IConnectionPointContainerImpl<CoVFW>,
  public CProxy_oVFWEvents<CoVFW>,
  public ISpecifyPropertyPagesImpl<CoVFW>
{

Back to Article

Listing Four

EXTERN_C const IID DIID__oVFWEvents= 
   { 0x54526101, 0xf0ca, 0x11d1, 
   { 0x96, 0x9f, 0x0, 0x20, 0x18, 
     0x63, 0x16, 0x32 } };

Back to Article

Listing Five

BEGIN_CONNECTION_POINT_MAP(CoVFW)
    CONNECTION_POINT_ENTRY(DIID__oVFWEvents)
END_CONNECTION_POINT_MAP()

Back to Article

Listing Six

pDriverInfo= new CComObject<CoVFWDriverInfo>;
dynamic_cast<IoVFWDriverInfo*>(pDriverInfo)->AddRef();

Back to Article

Listing Seven

dynamic_cast<IoVFWDriverInfo*>(pDriverInfo)->Release();

Back to Article

Listing Eight

return Error( 
_T("oVFWDriverInfo: bad driver information object"), 
IID_IoVFWDriverInfo, CUSTOM_CTL_SCODE(1009));

Back to Article

Listing Nine

Set Picture1.Picture = Clipboard.GetData(vbCFDIB)

Back to Article

Listing Ten

  ...RGS stuff here...
      ForceRemove {5A5FFDB1-F0A7-11D1-969F-002018631632} = s 'oVFW control'
     {
  ...additional RGS stuff here...
    ForceRemove 'Implemented Categories'
    {
         ForceRemove '{7DD95801-9882-11CF-9FA9-00AA006C42C4}'
         ForceRemove '{7DD95802-9882-11CF-9FA9-00AA006C42C4}'
        }
     }

Back to Article

Listing Eleven

<script language="VBScript">
<!--
Sub window_onload()
   vfw.ShowLiveVideo true
end sub
-->
</script>

<p><object id="vfw" name="vfw"
classid="clsid:5A5FFDB1-F0A7-11D1-969F-002018631632"
align="baseline" border="0" width="320" height="240" h="240"
w="320">Ofer LaOr - VFW</object> </p>

Back to Article


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.