MME Buddy

From ReactOS Wiki
Jump to: navigation, search

In Windows 3.1 (which the MME API was originally implemented on), it was possible to communicate directly with the hardware. For example, there was sndblst.drv, which takes requests from the MME API and directly controls a Sound Blaster card.

With the release of Windows NT, which of course prohibits user-mode applications from directly communicating with the hardware, it was necessary to split sound card drivers into two parts - one part runs in user-mode, the other in kernel-mode.

This explains why, for a Sound Blaster sound card, in Windows NT you have two files instead of one:

  • sndblst.dll
  • sndblst.sys

There is also a component named mmdrv.dll which is a generic user-mode sound driver support library. It does the same job as sndblst.dll except is not specific to Sound Blaster sound cards.

I'm not 100% certain of the details behind the existence of mmdrv.dll, as in NT it's referred to as "Low-level audio" and, if I recall correctly, this also existed on Windows 3.1

The differences I have found so far is that: - mmdrv.dll operates on devices such as \\.\WaveOut0 whilst sndblst.dll on \\.\SBWaveOut0 - sndblst.dll refers to strings in its resources to provide the product name

More recent versions of Windows include a component called wdmaud.drv which just provides access to the sound devices available via Kernel Streaming (ie, following the WDM audio driver model).

All of these user-mode components do pretty much the same job, so it makes sense to extract the common functionality and place it in a library - currently called MME Buddy. Indeed, this is the way in which it used to be done with the Windows NT4 DDK. However, we're going to need to support plug and play etc. and the way in which MME Buddy is being designed allows flexibility as far as functionality is concerned.


Implementation Status

Wave Output

Message Corresponding MME API Purpose Supported? Notes
WODM_BREAKLOOP waveOutBreakLoop Stop looping Yes
WODM_CLOSE waveOutClose Close a device Yes
WODM_GETDEVCAPS waveOutGetDevCaps Retrieve information about a device Yes
WODM_GETNUMDEVS waveOutGetNumDevs Obtain the number of devices Yes
WODM_GETPITCH waveOutGetPitch Get the current playback pitch No Never used this
WODM_GETPLAYBACKRATE waveOutGetPlaybackRate Get the current playback rate No Never used this
WODM_GETPOS waveOutGetPosition Obtain the current playback position in various time formats No
WODM_GETVOLUME waveOutGetVolume Get the current playback volume No Never used this
WODM_OPEN waveOutOpen Open a device Yes
WODM_PAUSE waveOutPause Pause playback Yes
WODM_PREFERRED ? ? No
WODM_PREPARE waveOutPrepareHeader Prepare a header describing a wave buffer for playback No Relying on WinMM for this functionality at present.
WODM_RESET waveOutReset Stop playback of audio Partially Currently this is supported by setting the current audio buffer to NULL.
WODM_RESTART waveOutRestart Continue playback of audio Yes
WODM_SETPITCH waveOutSetPitch Set the playback pitch No Never used this
WODM_SETPLAYBACKRATE waveOutSetPlaybackRate Set the playback rate No Never used this
WODM_SETVOLUME waveOutSetVolume Set the playback volume No Never used this
WODM_UNPREPARE waveOutUnprepareHeader Clean-up a wave audio header No Relying on WinMM for this functionality at present
WODM_WRITE waveOutWrite Submit a header describing a wave audio buffer Yes
DRVM_EXIT n/a ? No
DRVM_INIT n/a ? No

Wave Input

Message Corresponding MME API Purpose Supported? Notes
WIDM_ADDBUFFER waveInAddBuffer Submit a header describing a buffer to record wave audio into No
WIDM_CLOSE waveInClose Close a device No
WIDM_GETDEVCAPS waveInGetDevCaps Retrieve information about a device No
WIDM_GETNUMDEVS waveInGetNumDevs Obtain the number of devices No
WIDM_GETPOS waveInGetPosition Obtain the current recording position in various time formats No
WIDM_OPEN waveInOpen Open a device No
WIDM_PREFERRED ? ? No
WIDM_PREPARE waveInPrepareHeader Prepare a header describing a wave buffer for recording No Relying on WinMM for this functionality at present.
WIDM_RESET waveInReset No
WIDM_START waveInStart Begin recording No
WIDM_STOP waveInStop Stop recording No
WODM_UNPREPARE waveInUnprepareHeader Clean-up a wave audio header No Relying on WinMM for this functionality at present
DRVM_EXIT n/a ? No
DRVM_INIT n/a ? No

MIDI Output

Message Corresponding MME API Purpose Supported? Notes
MODM_CLOSE midiOutClose Close a device No
MODM_GETDEVCAPS midiOutGetDevCaps Retrieve information about a device No
MODM_GETNUMDEVS midiOutGetNumDevs Obtain the number of devices No
MODM_OPEN midiOutOpen Open a device No
DRVM_EXIT n/a ? No
DRVM_INIT n/a ? No

This list is incomplete

MIDI Input

Message Corresponding MME API Purpose Supported? Notes
MIDM_CLOSE midiInClose Close a device No
MIDM_GETDEVCAPS midiInGetDevCaps Retrieve information about a device No
MIDM_GETNUMDEVS midiInGetNumDevs Obtain the number of devices No
MIDM_OPEN midiInOpen Open a device No
DRVM_EXIT n/a ? No
DRVM_INIT n/a ? No

This list is incomplete

Mixer

Message Corresponding MME API Purpose Supported? Notes
MXDM_CLOSE mixerClose Close a device No
MXDM_GETCONTROLDETAILS mixerGetControlDetails get details of a control No
MXDM_GETDEVCAPS mixerGetDevCaps Retrieve information about a device Yes
MXDM_GETLINECONTROLS mixerGetLineControls gets information about a control No
MXDM_GETLINEINFO mixerGetLineInfo gets information about a source / destination line Yes
MXDM_GETNUMDEVS mixerGetNumDevs Obtain the number of devices Yes
MXDM_OPEN mixerOpen Open a device Yes
MXDM_SETCONTROLDETAILS mixerSetControlDetails sets details of a control No
DRVM_EXIT n/a ? No
DRVM_INIT n/a ? No
DRVM_MAPPER n/a ? No

Auxiliary

Message Corresponding MME API Purpose Supported? Notes
AUXDM_GETDEVCAPS auxGetDevCaps Retrieve information about a device No
AUXDM_GETNUMDEVS auxGetNumDevs Obtain the number of devices No
AUXDM_GETVOLUME auxGetVolume Retrieve the current volume No
AUXDM_SETVOLUME auxSetVolume Set the volume No
DRVM_EXIT n/a ? No
DRVM_INIT n/a ? No

Other Features / Bugs / To Do

  • Setting WinMM data on opening a device should be dealt with in a subroutine.
  • Queued buffers never get removed, even after completion.
  • WODM_RESET could be implemented in a cleaner fashion.
  • NT4's own MME components dont fire WOM_DONE until closure if breaking out of a loop, under some circumstances. I think, when the loop ends or is broken, as the end loop header is completed, we should send WOM_DONE for each header starting from the beginning of the loop.
  • Undocumented DRV_QUERYDRVENTRY, DRV_QUERYDEVNODE, DRV_QUERYNAME, DRV_QUERYDRIVERIDS, DRV_QUERYMAPPABLE, DRVM_MAPPER_PREFERRED_GET, DRV_QUERYDEVICEINTERFACE, DRV_QUERYDEVICEINTERFACESIZE, DRV_QUERYDSOUNDIFACE, DRV_QUERYDSOUNDDESC
  • How should writing the same WAVEHDR repeatedly be dealt with?


Development Notes

MSDN warns against, for example, calling waveOutWrite from inside a WOM_DONE callback, as it can cause a deadlock. My testing however shows that, at least on Windows XP, this does not hold true. Due to the current threading mechanism I have implemented, this situation will deadlock.

This is because WOM_DONE gets called within the same thread as the streaming code, and waveOutWrite waits to synchronise with the stream before it submits buffers. The streaming thread will never be "ready" so long as it is running a WOM_DONE routine, hence it never finishes.

This can be tackled in one of two ways: - Build a queue of new buffers outside of the streaming thread, set an event when there are buffers in this queue and protect it with a mutex. - Or, spawn a second thread responsible solely for dispatching "done" callbacks.


Mutex-protected buffer queue

The former of these could use the buffer queue already in the SoundDeviceInstance structure, which does however mean guarding it at every possible point at which it's used. If all validation and initialisation for a WAVEHDR is done prior to actually enqueueing the buffer, there'd be no reason to hang around for an MMRESULT unless waiting for the outcome of the streaming operation...


"Done" buffer callback dispatcher

The way this would work would be as follows: - The driver returns a buffer - MME Buddy's completion routine calculates how much data has been written for the current header - If a buffer has been completed, and it's not part of a loop, wait for a callback-thread-ready event - When we resume, fill a structure with the WAVEHDR and other relevant details (this is a static global structure) - Set another event (callback-thread-work?) - Wait on another event (callback-thread-done?)

In the meantime: - The callback thread sets callback-thread-ready event - It then waits for callback-thread-work event - The structure filled by the other thread gets copied - Set the callback-thread-done event - Do the callback

This basically means that the streaming thread doesn't have to wait whilst WOM_DONE gets processed - it can simply pass the details of the callback to be done and have it dealt with asynchronously.

The only thing to be careful of with this is that the waveOutWrite code for example is not callable in a thread-safe fashion!