Working with the Wave API for Windows and Windows Mobile via the .NET Framework

There are a number of samples out there that show how to work with the Wave API (ie sound recording and playback) on both Windows and Windows Mobile via either the .NET Framework or the .NET Compact Framework.  Unfortunately because of subtle differences between the platforms it is difficult to build a single library that works across both platforms. Over a series of posts I’m going to show you how to work with the api to build a library that will work across both platforms.

The starting point is to look at the api calls themselves.  There is plenty of documentation out there that discusses what each of the calls are supposed to do but essentially the api calls are the same between the platforms with the exception that they reside in different assemblies.  As they are native calls they need to be imported using a DllImport.  Here are most of the Windows (Desktop suffix) and Windows Mobile (CF suffix) imports – there are some additional api calls that you can include if you require them following a similar pattern.

[DllImport("coredll.dll", EntryPoint = "waveInGetNumDevs")]
internal static extern int waveInGetNumDevs_CF();

[DllImport("winmm.dll", EntryPoint = "waveInGetNumDevs")]
internal static extern int waveInGetNumDevs_Desktop();

[DllImport("coredll.dll", EntryPoint = "waveInOpen")]
internal static extern MMSYSERR waveInOpen_CF(out IntPtr phwi, uint uDeviceID, ref WAVEFORMATEX pwfx, IntPtr callback, uint dwInstance, uint fdwOpen);
[DllImport("winmm.dll", EntryPoint = "waveInOpen")]
internal static extern MMSYSERR waveInOpen_Desktop(out IntPtr phwi, uint uDeviceID, ref WAVEFORMATEX pwfx, IntPtr callback, uint dwInstance, uint fdwOpen);

[DllImport("coredll.dll", EntryPoint = "waveInPrepareHeader")]
internal static extern MMSYSERR waveInPrepareHeader_CF(IntPtr hwi, IntPtr pwh, uint cbwh);

[DllImport("winmm.dll", EntryPoint = "waveInPrepareHeader")]
internal static extern MMSYSERR waveInPrepareHeader_Desktop(IntPtr hwi, IntPtr pwh, uint cbwh);

[DllImport("coredll.dll", EntryPoint = "waveInUnprepareHeader")]
internal static extern MMSYSERR waveInUnprepareHeader_CF(IntPtr hwi, IntPtr pwh, uint cbwh);

[DllImport("winmm.dll", EntryPoint = "waveInUnprepareHeader")]
internal static extern MMSYSERR waveInUnprepareHeader_Desktop(IntPtr hwi, IntPtr pwh, uint cbwh);

[DllImport("coredll.dll", EntryPoint = "waveInClose")]
internal static extern MMSYSERR waveInClose_CF(IntPtr hwi);

[DllImport("winmm.dll", EntryPoint = "waveInClose")]
internal static extern MMSYSERR waveInClose_Desktop(IntPtr hwi);

[DllImport("coredll.dll", EntryPoint = "waveInReset")]
internal static extern MMSYSERR waveInReset_CF(IntPtr hwi);

[DllImport("winmm.dll", EntryPoint = "waveInReset")]
internal static extern MMSYSERR waveInReset_Desktop(IntPtr hwi);

[DllImport("coredll.dll", EntryPoint = "waveInStart")]
internal static extern MMSYSERR waveInStart_CF(IntPtr hwi);

[DllImport("winmm.dll", EntryPoint = "waveInStart")]
internal static extern MMSYSERR waveInStart_Desktop(IntPtr hwi);

[DllImport("coredll.dll", EntryPoint = "waveInStop")]
internal static extern MMSYSERR waveInStop_CF(IntPtr hwi);

[DllImport("winmm.dll", EntryPoint = "waveInStop")]
internal static extern MMSYSERR waveInStop_Desktop(IntPtr hwi);

[DllImport("coredll.dll", EntryPoint = "waveInAddBuffer")]
internal static extern MMSYSERR waveInAddBuffer_CF(IntPtr hwi, IntPtr pwh, uint cbwh);

[DllImport("winmm.dll", EntryPoint = "waveInAddBuffer")]
internal static extern MMSYSERR waveInAddBuffer_Desktop(IntPtr hwi, IntPtr pwh, uint cbwh);

[DllImport("coredll.dll", EntryPoint = "waveInGetDevCaps")]
internal static extern MMSYSERR waveInGetDevCaps_CF(uint uDeviceId, byte[] pwic, uint cbwic);

[DllImport("winmm.dll", EntryPoint = "waveInGetDevCaps")]
internal static extern MMSYSERR waveInGetDevCaps_Desktop(uint uDeviceId, byte[] pwic, uint cbwic);

[DllImport("coredll.dll", EntryPoint = "waveOutGetNumDevs")]
internal static extern int waveOutGetNumDevs_CF();

[DllImport("winmm.dll", EntryPoint = "waveOutGetNumDevs")]
internal static extern int waveOutGetNumDevs_Desktop();

[DllImport("coredll.dll", EntryPoint = "waveOutOpen")]
internal static extern MMSYSERR waveOutOpen_CF(out IntPtr phwo, uint uDeviceID, ref WAVEFORMATEX pwfx, IntPtr dwCallback, uint dwInstance, uint fdwOpen);
[DllImport("winmm.dll", EntryPoint = "waveOutOpen")]        internal static extern MMSYSERR waveOutOpen_Desktop(out IntPtr phwo, uint uDeviceID, ref WAVEFORMATEX pwfx, IntPtr dwCallback, uint dwInstance, uint fdwOpen);        /// <summary>

[DllImport("coredll.dll", EntryPoint = "waveOutPrepareHeader")]
internal static extern MMSYSERR waveOutPrepareHeader_CF(IntPtr hwo, IntPtr pwh, uint cbwh);

[DllImport("winmm.dll", EntryPoint = "waveOutPrepareHeader")]
internal static extern MMSYSERR waveOutPrepareHeader_Desktop(IntPtr hwo, IntPtr pwh, uint cbwh);

[DllImport("coredll.dll", EntryPoint = "waveOutWrite")]
internal static extern MMSYSERR waveOutWrite_CF(IntPtr hwo, IntPtr pwh, uint cbwh);

[DllImport("winmm.dll", EntryPoint = "waveOutWrite")]
internal static extern MMSYSERR waveOutWrite_Desktop(IntPtr hwo, IntPtr pwh, uint cbwh);

[DllImport("coredll.dll", EntryPoint = "waveOutUnprepareHeader")]
internal static extern MMSYSERR waveOutUnprepareHeader_CF(IntPtr hwo, IntPtr pwh, uint cbwh);

[DllImport("winmm.dll", EntryPoint = "waveOutUnprepareHeader")]
internal static extern MMSYSERR waveOutUnprepareHeader_Desktop(IntPtr hwo, IntPtr pwh, uint cbwh);

[DllImport("coredll.dll", EntryPoint = "waveOutClose")]
internal static extern MMSYSERR waveOutClose_CF(IntPtr hwo);

[DllImport("winmm.dll", EntryPoint = "waveOutClose")]
internal static extern MMSYSERR waveOutClose_Desktop(IntPtr hwo);

[DllImport("coredll.dll", EntryPoint = "waveOutReset")]
internal static extern MMSYSERR waveOutReset_CF(IntPtr hwo);

[DllImport("winmm.dll", EntryPoint = "waveOutReset")]
internal static extern MMSYSERR waveOutReset_Desktop(IntPtr hwo);

[DllImport("coredll.dll", EntryPoint = "waveOutGetDevCaps")]
internal static extern MMSYSERR waveOutGetDevCaps_CF(uint uDeviceID, byte[] pwoc, uint cbwoc);

[DllImport("winmm.dll", EntryPoint = "waveOutGetDevCaps")]
internal static extern MMSYSERR waveOutGetDevCaps_Desktop(uint uDeviceID, byte[] pwoc, uint cbwoc);

You will notice that the Windows Mobile calls all reference coredll.dll, whilst the Windows calls reference winmm.dll.  In order to build a common library we need a way to abstract these calls so that after initialising the library we never have to worry about whether it’s running on a device or a desktop machine. To do this we just need to define a series of delegates that map to each of these functions, and then create instances of them that point to the relevant functions.

public delegate int HardwareDeviceCount();
public delegate MMSYSERR HardwareOpen(out IntPtr hardwareInterface, uint deviceId, ref WAVEFORMATEX waveFormatSettings, IntPtr callbackFunction, uint callbackData, uint callbackType);
public delegate MMSYSERR HardwareGetDeviceCaps(uint deviceId, byte[] data, uint dataSize);
public delegate MMSYSERR HardwarePrepareHeader(IntPtr hardwareInterface, IntPtr headerReference, uint headerSize);
public delegate MMSYSERR HardwareAddBuffer(IntPtr hardwareInterface, IntPtr bufferReference, uint bufferSize);
public delegate MMSYSERR HardwareStart(IntPtr hardwareInterface);
public delegate MMSYSERR HardwareStop(IntPtr hardwareInterface);
public delegate MMSYSERR HardwareUnprepareHeader(IntPtr hardwareInterface, IntPtr headerReference, uint headerSize);
public delegate MMSYSERR HardwareClose(IntPtr hardwareInterface);
public delegate MMSYSERR HardwareReset(IntPtr hardwareInterface);
public delegate MMSYSERR HardwareGetPosition(IntPtr hardwareInterface, ref MMTIME timeReference, uint timeSize);
public delegate MMSYSERR HardwareGetId(IntPtr hardwareInterface, ref uint deviceOutput);

private static HardwareDeviceCount waveInGetNumDevs;
private static HardwareOpen waveInOpen;
private static HardwareGetDeviceCaps waveInGetDevCaps;
private static HardwarePrepareHeader waveInPrepareHeader;
private static HardwareUnprepareHeader waveInUnprepareHeader;
private static HardwareAddBuffer waveInAddBuffer;
private static HardwareStart waveInStart;
private static HardwareClose waveInClose;
private static HardwareStop waveInStop;
private static HardwareReset waveInReset;

private static HardwareDeviceCount waveOutGetNumDevs;
private static HardwareOpen waveOutOpen;
private static HardwareReset waveOutReset;

private static HardwareGetDeviceCaps waveOutGetDevCaps;
private static HardwarePrepareHeader waveOutPrepareHeader;
private static HardwareUnprepareHeader waveOutUnprepareHeader;
private static HardwareClose waveOutClose;
private static HardwareWrite waveOutWrite;

private static void SetupDesktopDelegates()
{
    waveOutGetNumDevs = NativeMethods.waveOutGetNumDevs_Desktop;
    waveOutOpen = NativeMethods.waveOutOpen_Desktop;
    waveOutReset = NativeMethods.waveOutReset_Desktop;

    waveOutGetDevCaps = NativeMethods.waveOutGetDevCaps_Desktop;
    waveOutPrepareHeader = NativeMethods.waveOutPrepareHeader_Desktop;
    waveOutUnprepareHeader = NativeMethods.waveOutUnprepareHeader_Desktop;
    waveOutClose = NativeMethods.waveOutClose_Desktop;
    waveOutWrite = NativeMethods.waveOutWrite_Desktop;

    waveDataCallback = QueryOutgoingData;
    waveDataCallbackPointer = Marshal.GetFunctionPointerForDelegate(waveDataCallback);
    CALLBACK_TYPE = Wave.CALLBACK_FUNCTION;
}

private static void SetupCompactDelegates()
{
    waveOutGetNumDevs = NativeMethods.waveOutGetNumDevs_CF;
    waveOutOpen = NativeMethods.waveOutOpen_CF;
    waveOutReset = NativeMethods.waveOutReset_CF;

    waveOutGetDevCaps = NativeMethods.waveOutGetDevCaps_CF;
    waveOutPrepareHeader = NativeMethods.waveOutPrepareHeader_CF;
    waveOutUnprepareHeader = NativeMethods.waveOutUnprepareHeader_CF;
    waveOutClose = NativeMethods.waveOutClose_CF;
    waveOutWrite = NativeMethods.waveOutWrite_CF;

    WaveLibrary.Native.CF.SoundMessageWindow window = Wave.SetupMessageWindow();
    window.WaveOutDoneMessage += new WaveDoneEventHandler(MessageWindow_WaveDoneMessage);
    waveDataCallbackPointer = window.Hwnd;
    CALLBACK_TYPE = Wave.CALLBACK_WINDOW;
}

In this code we have declared the set of delegates, a set of static instances and then have a two methods that create instances pointing to either the Windows or Windows Mobile imported functions.  Note that it’s important to have these abstracted into separate methods as the library will throw an exception when it attempts to reference a dll that can’t be located.  Having these calls in a separate function means that only the called function will be JIT’d meaning the exception won’t be thrown.

if (useDesktop)
            {
                SetupDesktopDelegates();
            }
            else
            {
                SetupCompactDelegates();
            }

You can simply call the relevant setup function based on whether you are running on the desktop or a device.

Leave a comment