Specifying Waveform Data Formats

When you call waveOutOpen to open a device driver for playback or to query if the driver supports a particular data format, use the lpFormat parameter to specify a pointer to a structure containing the requested waveform data format.

Summary: The WAVEFORMAT Structure

The WAVEFORMAT structure specifies format information common to all types of waveform data formats. Currently, the only format supported is PCM, but in the future, other types such as ADPCM might be supported. The MMSYSTEM.H file defines the WAVEFORMAT structure as follows:

typedef struct waveformat_tag {
        WORD        wFormatTag;                        /* format type */
        WORD        nChannels;                        /* number of channels */
        DWORD        nSamplesPerSec;                /* number of samples per second */
        DWORD        nAvgBytesPerSec;        /* average data rate */
        WORD        nBlockAlign;                /* block alignment */
} WAVEFORMAT;

The wFormatTag field specifies the format type for the data. Currently, the only flag defined for this field is WAVE_FORMAT_PCM for PCM waveform data.

The nChannels field specifies the number of discrete channels in the format. Use a value of 1 for mono data and 2 for stereo data.

The nSamplesPerSec field specifies the sample rate.

The nAvgBytesPerSec field specifies the average data rate in bytes per second. For example, 16-bit stereo at 44.1 kHz has an average data rate of 176400 bytes per second (2 channels × 2 bytes per sample per channel × 44100 samples per second).

The nBlockAlign field specifies the minimum atomic unit of data that can be passed to a driver. For PCM data, the block alignment is the number of bytes used by a single sample, including data for both channels if the data is stereo. For example, the block alignment for 16-bit stereo PCM is 4 bytes (2 channels × 2 bytes per sample).

Summary: The PCMWAVEFORMAT Structure

In addition to the general information in the WAVEFORMAT structure, specific information is needed to completely describe a waveform data format. For PCM waveform data, the PCMWAVEFORMAT structure includes a WAVEFORMAT structure along with an additional field containing PCM-specific information as follows.

typedef struct pcmwaveformat_tag {
    WAVEFORMAT  wf;                 /* general format information */
    WORD        wBitsPerSample;     /* number of bits per sample */
} PCMWAVEFORMAT;

The wf field specifies general format information. The wBitsPerSample field specifies the number of bits per sample for PCM data.

Using the PCMWAVEFORMAT Structure

Use the PCMWAVEFORMAT structure to specify the format for PCM audio data. The following code fragment shows how to set up a PCMWAVEFORMAT structure for 11.025 kHz 8-bit mono and for 44.1 kHz 16-bit stereo. After setting up PCMWAVEFORMAT, the example calls the IsFormatSupported function to verify that the waveform output device supports the format. The source for IsFormatSupported is given in an example in “Determining Non-Standard Format Support,” earlier in this chapter.

WORD wReturn;
PCMWAVEFORMAT pcmWaveFormat;

/* Set up PCMWAVEFORMAT for 11 kHz 8-bit mono
 */
pcmWaveFormat.wf.wFormatTag = WAVE_FORMAT_PCM;
pcmWaveFormat.wf.nChannels = 1;
pcmWaveFormat.wf.nSamplesPerSec = 11025L;
pcmWaveFormat.wf.nAvgBytesPerSec = 11025L;
pcmWaveFormat.wf.nBlockAlign = 1;
pcmWaveFormat.wBitsPerSample = 8;
   
/* See if format is supported by any device in system
 */
wReturn = IsFormatSupported(&pcmWaveFormat, WAVE_MAPPER);

/* Report results
 */
if (wReturn == 0)
     MessageBox(hMainWnd, "11 kHz 8-bit mono is supported.",
       "", MB_ICONINFORMATION);
else if (wReturn == WAVERR_BADFORMAT)
     MessageBox(hMainWnd, "11 kHz 8-bit mono is NOT supported.",
       "", MB_ICONINFORMATION);
else
     MessageBox(hMainWnd, "Error opening waveform device.",
       "Error", MB_ICONEXCLAMATION);
       

/* Set up PCMWAVEFORMAT for 44.1 kHz 16-bit stereo
 */
pcmWaveFormat.wf.wFormatTag = WAVE_FORMAT_PCM;
pcmWaveFormat.wf.nChannels = 2;
pcmWaveFormat.wf.nSamplesPerSec = 44100L;
pcmWaveFormat.wf.nAvgBytesPerSec = 44100L;
pcmWaveFormat.wf.nBlockAlign = 4;
pcmWaveFormat.wBitsPerSample = 16;
 
/* See if format is supported by any device in the system
 */
wReturn = IsFormatSupported(&pcmWaveFormat, WAVE_MAPPER);

/* Report results
 */
If (wReturn == 0)
    MessageBox(hMainWnd, "44.1 kHz 16-bit stereo is supported.",
      "", MB_ICONINFORMATION);
else if (wReturn == WAVERR_BADFORMAT)
    MessageBox(hMainWnd, "44.1 kHz 16-bit stereo is NOT supported.",
      "", MB_ICONINFORMATION);
else
    MessageBox(hMainWnd, "Error opening waveform device.",
      "Error", MB_ICONEXCLAMATION);

Getting Format Information from a WAVE File

The easiest way to get waveform-format information from a WAVE file is by using the Multimedia file I/O services. To do this, use mmioDescend to locate the “fmt ” chunk containing the format information and then mmioRead to read the format chunk directly into the proper format structure (chunks are the basic building blocks of RIFF files). The following code fragment illustrates this technique. For more information about Multimedia file I/O, see Chapter 10, “Multimedia File I/O Services.”

void ReversePlay()
{
    HMMIO       hmmio;
    MMCKINFO    mmckinfoParent;
    MMCKINFO    mmckinfoSubchunk;
    DWORD       dwFmtSize;
    char        szFileName[ MAX_FILENAME_SIZE ];
    HANDLE      hFormat;
    WAVEFORMAT  *pFormat;
        .
        .
        .
   
    /* Open the given file for reading using buffered I/O.
     */
        ...

    /* Locate a "RIFF" chunk with a "WAVE" form type
     * to make sure it's a WAVE file.
     */
        ...
   
    /* Now, find the format chunk (form type "fmt "). It should be
     * a subchunk of the "RIFF" parent chunk.
     */
    mmckinfoSubchunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
    if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent,
        MMIO_FINDCHUNK))
    {
        MessageBox(hwndApp, "WAVE file is corrupted.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        mmioClose(hmmio, 0);
        return;
    }

    /* Get the size of the format chunk, allocate and lock memory for it.
     */
    dwFmtSize = mmckinfoSubchunk.cksize;
    hFormat = LocalAlloc(LMEM_MOVEABLE, LOWORD(dwFmtSize));
    if (!hFormat)
    {
        MessageBox(hwndApp, "Out of memory.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        mmioClose(hmmio, 0);
        return;
    }
    pFormat = (WAVEFORMAT *) LocalLock(hFormat);
    if (!pFormat)
    {
        MessageBox(hwndApp, "Failed to lock memory for format chunk.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        LocalFree(hFormat);
        mmioClose(hmmio, 0);
        return;
    }





    /* Read the format chunk.
     */
    if (mmioRead(hmmio, pFormat, dwFmtSize) != dwFmtSize)
    {
        MessageBox(hwndApp, "Failed to read format chunk.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        LocalUnlock(hFormat);
        LocalFree(hFormat);
        mmioClose(hmmio, 0);
        return;
    }

    /* Make sure it's a PCM file.
     */
    if (pFormat->wFormatTag != WAVE_FORMAT_PCM)
    {
        LocalUnlock(hFormat);
        LocalFree(hFormat);
        mmioClose(hmmio, 0);
        MessageBox(hwndApp, "The file is not a PCM file.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }

        /* Make sure the system has a waveform output
     * device capable of playing this format.
         */
        if (waveOutOpen(&hWaveOut, WAVE_MAPPER, (LPWAVEFORMAT)pFormat, NULL,
                    0L, WAVE_FORMAT_QUERY))
        {
        LocalUnlock(hFormat);
        LocalFree(hFormat);
        mmioClose(hmmio, 0);
        MessageBox(hwndApp, "The waveform device can't play this format.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
        }
    .
    .
    .
}