This chapter includes:
Before discussing the organization of the driver you're about to write, it's useful to first discuss the organization of the QNX audio system. Let's consider an audio application sending data to an audio card. The QNX architecture for this is:
Everything to the right of the Process memory boundary is the audio driver. To complete the audio driver for your audio card, you have to build only the Audio HW DLL.
In addition to this, the client library has a series of plugins that the application can use to convert its data to and from various formats. This means the driver has to support only native audio formats. The client library plugins can be used to make any necessary conversions. The mechanism by which this is done is somewhat complicated and outside the scope of this document, but it does allow for a simplified driver interface.
When you install the DDK package, the source is put into a directory under the ddk_install_dir/ddk-audio directory. Currently, the directory structure for the Audio DDK looks like this:
For example, the ddk_working_dir for the previous example would be ~/my_DDKs/audio/.
This section describes some of the basics of writing an Audio HW DLL, including:
The API for the Audio DDK involves some structures that aren't defined in the scope of this DDK; their contents are only known to the io-audio layer. You typically just need to save pointers to them and pass the pointers to the functions that need them.
Here's a list of the opaque data types:
The ado prefix to these names stands for audio.
Your Audio HW DLL might need to keep some internal data for its own use. The Audio DDK lets you define context-sensitive data for your hardware as well as the mixers.
To make the API more flexible (and readable), the Audio DDK uses these types that you can define as you wish:
By default, these types are empty structures. Use a #define directive to set these types as appropriate before including any of the Audio DDK header files. For example:
#define HW_CONTEXT_T my_hw_context_t #define MIXER_CONTEXT_T my_mixer_context_t
If you wish, you can even define HW_CONTEXT_T and MIXER_CONTEXT_T to be the same type.
|The MIXER_CONTEXT_T is stored as part of the ado_mixer_t structure. If you need to access the mixer context, you need to call ado_mixer_get_context() because ado_mixer_t is an opaque data type.|
Your Audio HW DLL must provide an entry point called ctrl_init(), of type ado_ctrl_dll_init_t. When io-audio loads your Audio HW DLL, it calls this function. The prototype is:
int32_t ctrl_init( HW_CONTEXT_T **hw_context, ado_card_t *card, char *args )
The arguments are:
This structure isn't defined in the scope of this DDK; its contents are only known to the io-audio layer. You'll need to save this pointer to pass to some functions, such as ado_mixer_create(), that your Audio HW DLL might call.
The first job of this initialization code is to allocate its context-specific state structure. You can allocate the hardware context by calling ado_calloc() or ado_malloc().
Next, you need to verify that the hardware is present in the system. How you do this depends on your driver; in some cases, it isn't possible:
The ado_pci_device() function returns a pointer to a ado_pci structure that describes a selected PCI card. Keep a copy of this pointer in your hardware context.
You should set the short and long names that audio applications will use to identify your card's type and the specific instance of the hardware; call ado_card_set_shortname() and ado_card_set_longname().
In addition to the above, the initialization depends on what features your Audio HW DLL supports:
If the initialization is successful, ctrl_init() should return 0. If an error occurs, ctrl_init() should return -1; in this case, io-audio unmounts the card.
Your Audio HW DLL must provide an entry point called ctrl_destroy(), of type ado_ctrl_dll_destroy_t. The io-audio manager calls ctrl_destroy() whenever the card is unmounted. The prototype is:
int32_t ctrl_destroy( HW_CONTEXT_T **hw_context );
This function undoes whatever you did in your ctrl_init() function. Typically, your ctrl_destroy() function:
If the cleanup is successful, ctrl_destroy() should return 0. If an error occurs, ctrl_destroy() should return -1.
The Audio DDK uses several constants to turn debugging messages on and off. The main one is ADO_DEBUG.
The standard Audio DDK makefiles define ADO_DEBUG if you've defined DEBUG in your environment. If you compile your driver with debugging options, ADO_DEBUG is defined as well to help you debug your logic.
If ADO_DEBUG is defined, several things happen:
You can call ado_memory_dump() to get a full listing of active memory. In nondebug mode, these memory functions are defined to be the standard C library functions.
If you've set ADO_DEBUG, you can also define the following macros to get specialized debug output:
If you define these macros, you'll see a message whenever a lock is changed. You'll probably need to define them only if you encounter a locking problem.
|If you've compiled your driver with ADO_DEBUG on, the driver won't run under the shipped io-audio, because the debugging code makes io-audio bigger, which can be a problem for embedded systems. If you want to run your driver in debug mode, use the io_audio_g that's shipped with the DDK.|