Writing ACM Drivers
The best way to write an ACM driver is to select one of the sample drivers provided with the DDK and modify the code as required to implement new conversions and features. The comments in the sample code provide a detailed explanation of the different responses to each message and indicate which parts need to be modified for a new driver.
Sample ACM Drivers
Three sample ACM drivers are provided with the DDK.
- The imaadpcm sample provides a speed-optimized codec which converts between PCM and the IMA ADPCM format. This codec shows how to use several conversion routines to reduce computational requirements. It also supports a configuration dialog box, and it automatically configures itself the first time it is loaded.
- The msfilter sample provides a simple volume and echo filter. (Both filters are supported within the same driver.) This driver supports a custom icon and a custom About dialog box. As noted earlier, the ACM provides a default icon and About dialog box for drivers which do not provide their own; using the default About dialog box is recommended, since the driver code is simpler and smaller.
- The gsm610 sample implements the GSM 6.10 voice encoding standard, originally developed for digital cellular-telephone encoding. It supports a configuration dialog box and automatic-configuration code.
Tips for Writing ACM Drivers
This section provides some guidelines for writing ACM drivers.
- Allocate driver-instance data in response to the DRV_OPEN message. You will usually need some data throughout the life of the driver; for example, you need the module-instance handle in order to load resource strings. Define a structure containing this data, and allocate it in response to DRV_OPEN. Do not save this structure as a global variable. Instead, return a pointer to the structure as the return value for the DRV_OPEN message. This pointer will be passed back to the driver in every subsequent message, as the first parameter of the DriverProc function. Remember to free this driver-instance data in response to the DRV_CLOSE message.
- Allocate stream-instance data in response to the ACMDM_STREAM_OPEN message. You will usually need some instance data for each open stream. Since there could be several streams open at the same time, you shouldn't store this data as a global variable. Instead, define a structure containing the required data, and allocate it in response to ACMDM_STREAM_OPEN. In that message, the driver is passed a pointer to an ACMDRVSTREAMINSTANCE structure. Store a pointer to the stream-instance data in the dwDriver member, and it will be available on every call involving that stream. Remember to free the stream-instance data in response to the ACMDM_STREAM_CLOSE message.
- Avoid using global variables. Defining a single DWORD of global data will use up 4K of memory in every process which uses the ACM, because your driver is loaded into each process. There is no need to use global data; instead, use the instance data, as described earlier.
- Make sure that your driver processes the ACMDM_STREAM_CONVERT message as quickly as possible. Perform as much processing as possible in the ACMDM_STREAM_OPEN message.
- Don't link to CRTDLL.DLL. This DLL cannot be loaded into all contexts, and therefore your codec will not function correctly. In particular, your codec may not work correctly for system sounds produced using the MessageBeep function. Use Win32 functions or link to a static C runtime library such as LIBC.LIB or LIBCMT.LIB.
- Handle Win32 error conditions correctly. You cannot always depend on the success of calls to the USER module (such as LoadString and LoadIcon). In particular, when your codec is used to play system sounds, it may be loaded into a context in which these calls are unable to succeed. Although there will be no opportunity for your codec to display icons or strings in this context, your code must be prepared handle this situation.
- Store configuration data on a per-user basis. Each user should be able to configure an ACM driver according to personal preferences. Therefore, configuration information should be stored in the HKEY_CURRENT_USER section of the registry.
- Provide a valid default configuration. Configuration data will normally be stored on a per-user basis; however, in some contexts (such as when playing system sounds) there will be no defined current user. In these situations, calls to the registry will fail. You must provide a reasonable default configuration to handle this failure.
Writing Portable ACM Drivers
Most of the features of the ACM in Windows 95 are also provided in Windows NT 3.5. If written correctly, 32-bit ACM drivers written for Windows 95 will run unmodified on Windows NT 3.5; it should not even be necessary to recompile them. A developer can simply ship the Windows 95 driver as the x86 Windows NT 3.5 driver. All the sample ACM drivers provided in the DDK offer binary compatibility with Windows NT 3.5, and the ACM drivers shipping with Windows 95 offer binary compatibility with Windows NT 3.5.
To help maintain compatibility between Windows platforms, use the private LoadStringCodec function instead of the LoadString function. LoadStringCodec is a private function included in the sample ACM drivers. Using this function (together with the SIZEOFACMSTR macro) will solve most of your Unicode problems.
Features available in the ACM in Windows 95 that are not available in Windows NT 3.5 include support for asynchronous conversions and support for Plug-and-Play. Both of these features are useful only for drivers that rely on hardware support.