Capturing Raw PCM Audio in Windows Mobile 6.5
Capturing raw Pulse Code Modulation (PCM) audio from the microphone
in Windows Mobile 6.5 relied primarily on the Win32 Waveform Audio
(waveIn) API. Built atop the Windows CE 5.2 kernel, the
operating system required developers to interface directly with
low-level audio drivers by configuring audio format headers, managing
circular memory buffers, and listening for hardware-driven callback
events. This article details the programmatic process and driver-level
interactions required to capture uncompressed audio streams on legacy
Windows Mobile hardware.
Configuring the Audio Format
To initiate recording, developers defined the target audio
characteristics using the WAVEFORMATEX structure. Because
the hardware needed explicit instructions on how to digitize analog
microphone signals, developers specified parameters such as:
- wFormatTag: Set to
WAVE_FORMAT_PCMto request uncompressed audio. - nChannels: Typically set to
1(mono) for voice recording via the built-in microphone, or2(stereo) if supported by the hardware. - nSamplesPerSec: Standard sampling rates included 8000 Hz, 11025 Hz, 22050 Hz, or 44100 Hz.
- wBitsPerSample: Configured to either 8-bit or 16-bit audio depth.
- nBlockAlign and nAvgBytesPerSec: Calculated directly from the channels and bit depth to inform the system how many bytes constituted an atomic sample block.
Opening the Waveform Device
The application called waveInOpen() to request a handle
to the input device, typically passing WAVE_MAPPER as the
device identifier so the operating system could route the call to the
default recording hardware.
Along with the format structure, waveInOpen() took a
notification mechanism. Windows Mobile developers generally chose
between window messaging (CALLBACK_WINDOW) or event handles
(CALLBACK_EVENT). On mobile devices with constrained CPU
cycles, event-driven callbacks (CALLBACK_EVENT) were
preferred because they avoided window message queue latency and allowed
a dedicated background worker thread to process incoming audio packets
efficiently.
Buffer Management and Queuing
The waveIn subsystem does not allocate recording memory
automatically. The developer had to allocate fixed memory blocks and
manage a double-buffering or ring-buffering strategy to prevent dropped
audio frames.
- Memory Allocation: Developers allocated multiple memory blocks (typically two or three buffers of a few kilobytes each) to hold raw audio data.
- Header Preparation: Each memory block was wrapped
inside a
WAVEHDRstructure, which tracked the buffer pointer (lpData), buffer length (dwBufferLength), and state flags. - Registration: The application called
waveInPrepareHeader()to lock the memory pages in physical RAM, ensuring the audio driver could perform direct memory access (DMA) without paging faults. - Enqueuing: Calling
waveInAddBuffer()handed ownership of the buffers over to the audio driver queue.
Driver Interaction and the Recording Loop
Once buffers were queued, calling waveInStart() signaled
the underlying driver—usually implemented via the Windows CE
WAVEDEV2 driver architecture—to activate the
analog-to-digital converter (ADC) linked to the microphone line.
As acoustic signals were digitized into raw PCM bytes:
- The hardware driver populated the currently active
WAVEHDRbuffer via DMA. - When a buffer filled completely, the driver set the
WHDR_DONEflag and signaled the callback event or sent theMM_WIM_DATAmessage. - The driver immediately moved to the next pre-queued buffer without stopping the capture process.
- The application’s background thread woke up, accessed the raw PCM
bytes directly from
WAVEHDR.lpData, and wrote them to storage or processed them in memory. - The application called
waveInUnprepareHeader(), re-initialized the buffer, calledwaveInPrepareHeader(), and passed it back to the driver usingwaveInAddBuffer().
Teardown and Resource Cleanup
To stop recording, the application called waveInStop()
to halt the ADC conversion, followed by waveInReset().
Calling waveInReset() marked all pending and partially
filled buffers as done, returning them to the application so no residual
samples were lost. Finally, developers unregistered all buffers with
waveInUnprepareHeader(), freed the allocated RAM, and
released the hardware interface with waveInClose().