Important alert: (current site time 9/30/2014 11:31:02 AM EDT)
 

article

Playing Audio in Windows using waveOut Interface

Email
Submitted on: 7/24/2002 11:32:52 AM
By: David Overton  
Level: Intermediate
User Rating: By 28 Users
Compatibility: C, Microsoft Visual C++
Views: 165079
 
     This tutorial will teach you how to use the Windows waveOut multimedia functions. It also explains a little about how audio is stored in the digital form. I hope this tutorial is useful. Full source code is included as is a downloadable version wrapped in an MSVC++ project.

 
 
Terms of Agreement:   
By using this article, you agree to the following terms...   
  1. You may use this article in your own programs (and may compile it into a program and distribute it in compiled format for languages that allow it) freely and with no charge.
  2. You MAY NOT redistribute this article (for example to a web site) without written permission from the original author. Failure to do so is a violation of copyright laws.   
  3. You may link to this article from another website, but ONLY if it is not wrapped in a frame. 
  4. You will abide by any additional copyright restrictions which the author may have placed in the article or article's description.
				

Windows waveOut Tutorial

This tutorial is designed to help you use the windows waveOut interface for playing digital audio. I know from experience that the interface can be pretty difficult to get to grips with. Through this tutorial I will build a windows console application for playing raw digital audio. This is my first tutorial so I'll apologise for the mistakes in advance!

Note: This tutorial assumes that you are competent with C programming and using the Windows API functions. A basic understanding of digital audio is useful but not completely necessary.

Contents

  • Get The Documentation!
  • What is Digital Audio?
  • Opening the Sound Device
  • Playing a Sound
  • Streaming Audio to the Device
  • The Buffering Scheme
  • The Driver Program
  • What Next?
  • Contacting Me

Get The Documentation!

The first thing you'll need is some decent documentation on the waveOut interface. If you have the Microsoft Platform SDK (a worthwhile download) or a copy of Visual C++ then you already have the relevent information in the documentation provided. If you don't have either of these you can view the documentation online at Microsoft's Developer website (msdn.microsoft.com).

What is Digital Audio?

This bit is for people who have absolutely no idea how digital audio is stored. Skip this section if you know all about digital audio and you know the meaning of the terms 'Sample', 'Sampling Rate', 'Sample Size', and 'Channels'.

It's all very well sending all these bytes to the sound card but what do these bytes mean? Audio is simply a series of moving pressure waves. In real life this is an analogue wave, but in the digital world we have to store it as a set of samples along this wave. A sample is a value that represents the amplitude of the wave at a given point in time - it's just a number.

The sampling rate is how frequently we take a sample of the wave. It is measured in hertz (Hz) or 'samples per second'. Obviously the higher the sampling rate, the more like the analogue wave your sampled wave becomes, so the higher the quality of the sound.

Another thing that contributes to the quality of the audio is the size of each sample. Yes, you guessed it. The larger the sample size the higher the quality of the audio. Sample size is measured in bits. Why is the quality better? Consider an 8 bit sample. It has 256 (2^8) possible values. This means that you may not be able to represent the exact amplitude of the wave with it. Now consider a 16 bit sample. It has 65536 possible values (2^16). This means that it is 256 times as accurate as the 8 bit sample and can thus represent the amplitude more accurately.

The final thing I'll touch on here is the channels. On most systems you have two speakers, left and right. That's two channels. This means that you must store a sample for the left channel and the right channel.
Fortunately this is easy for two channels (which is the most you'll encounter in this tutorial). The samples are interleaved. That is the samples are stored, left, right, left, right etc...

CD quality audio is sampled at 44100 Hz, has a sample size of 16 bits and has 2 channels. This means that 1 MB of audio data lasts for approximately 6 seconds.

Opening the Sound Device

To open the sound device you use the waveOutOpen function (look this up in your documentation now). Like most Windows objects, you basically need a handle to anything to use it. When you act on a window you use a HWND handle. Similarly when you act on a waveOut device you use a HWAVEOUT handle.

So now comes the first version of our application. This simply opens the wave device to a CD quality standard, reports what's happened and closes it again.

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
HWAVEOUT hWaveOut; /* device handle */
WAVEFORMATEX wfx; /* look this up in your documentation */
MMRESULT result;/* for waveOut return values */
/*
 * first we need to set up the WAVEFORMATEX structure. 
 * the structure describes the format of the audio.
 */
wfx.nSamplesPerSec = 44100; /* sample rate */
wfx.wBitsPerSample = 16; /* sample size */
wfx.nChannels = 2; /* channels*/
/*
 * WAVEFORMATEX also has other fields which need filling.
 * as long as the three fields above are filled this should
 * work for any PCM (pulse code modulation) format.
 */
wfx.cbSize = 0; /* size of _extra_ info */
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
/*
 * try to open the default wave device. WAVE_MAPPER is
 * a constant defined in mmsystem.h, it always points to the
 * default wave device on the system (some people have 2 or
 * more sound cards).
 */
if(waveOutOpen(
&hWaveOut, 
WAVE_MAPPER, 
&wfx, 
0, 
0, 
CALLBACK_NULL
) != MMSYSERR_NOERROR) {
fprintf(stderr, "unable to open WAVE_MAPPER device\n");
ExitProcess(1);
}
/*
 * device is now open so print the success message
 * and then close the device again.
 */
printf("The Wave Mapper device was opened successfully!\n");
waveOutClose(hWaveOut);
return 0;
}
 

Note that when compiling this program you will need to add winmm.lib to your list of library files or the linker will fail.

So that was the first step. The device was ready and waiting for you to write audio data to it.

Playing a Sound

Opening and closing the device is fun for a while but it doesn't actually do that much. What we want is to hear a sound. We need to do two things before this can happen.

  • Obtain a source of raw audio in the correct format
  • Work out how to write the data
Problem 1 is easy to solve. You can convert any music file into raw audio using a program like Winamp with the Disk Writer plug-in. Start small and convert one of the Windows sounds into a raw file. These files are located in your \Windows\Media directory. Ding.wav seems like a good choice to start with. If you can't convert this to a raw file you can have fun playing the unconverted file back instead. It will sound too fast since these files are mostly sampled at 22 kHz.

Problem 2 is slightly more tricky. Audio is written in blocks, each with its own header. It's easy to write one block but at some point we're going to have to come up with a scheme for queuing and writing many blocks. The reason I said to start with a small file is that the second version of our application will load the entire file as a single block.

We will first tackle Problem 2 by writing a function that will send a block of data to the audio device. The function will be called writeAudioBlock. To write audio data you use up to three functions. These are waveOutPrepareHeader, waveOutWrite, and waveOutUnprepareHeader and are called in the order I have listed them. It would be a good idea to look these up in your documentation now to familiarise yourself with them.

Here is the code for a preliminary version of the function writeAudioBlock

void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size)
{
WAVEHDR header;
/*
 * initialise the block header with the size
 * and pointer.
 */
ZeroMemory(&header, sizeof(WAVEHDR));
header.dwBufferLength = size;
header.lpData = block;
/*
 * prepare the block for playback
 */
waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
/*
 * write the block to the device. waveOutWrite returns immediately
 * unless a synchronous driver is used (not often).
 */
waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
/*
 * wait a while for the block to play then start trying
 * to unprepare the header. this will fail until the block has
 * played.
 */
Sleep(500);
while(waveOutUnprepareHeader(
hWaveOut, 
&header, 
sizeof(WAVEHDR)
) == WAVERR_STILLPLAYING)
Sleep(100);
}
 

Now we've got a function for writing a block of data we need a function for getting hold of one in the first place. That is the task of loadAudioBlock. loadAudioBlock will load a file into memory and return a pointer to it. Here is the code for loadAudioBlock.

LPSTR loadAudioBlock(const char* filename, DWORD* blockSize)
{
HANDLE hFile= INVALID_HANDLE_VALUE;
DWORD size = 0;
DWORD readBytes = 0;
void* block = NULL;
/*
 * open the file
 */
if((hFile = CreateFile(
filename,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
)) == INVALID_HANDLE_VALUE)
return NULL;
/*
 * get it's size, allocate memory and read the file
 * into memory. don't use this on large files!
 */
do {
if((size = GetFileSize(hFile, NULL)) == 0) 
break;
if((block = HeapAlloc(GetProcessHeap(), 0, size)) == NULL)
break;
ReadFile(hFile, block, size, &readBytes, NULL);
} while(0);
CloseHandle(hFile);
*blockSize = size;
return (LPSTR)block;
}
 

Finally for this section, here are the changes that must be made to the beginning of the file and to main.

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
LPSTR loadAudioBlock(const char* filename, DWORD* blockSize);
void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size);
int main(int argc, char* argv[])
{
HWAVEOUT hWaveOut; 
WAVEFORMATEX wfx; 
LPSTR block;/* pointer to the block */
DWORD blockSize;/* holds the size of the block */
 
.
. (leave middle section as it was) 
.
printf("The Wave Mapper device was opened successfully!\n");
/*
 * load and play the block of audio
 */
if((block = loadAudioBlock("c:\\temp\\ding.raw", &blockSize)) == NULL) {
fprintf(stderr, "Unable to load file\n");
ExitProcess(1);
}
writeAudioBlock(hWaveOut, block, blockSize); 
waveOutClose(hWaveOut);
return 0;
}
 

If you've put all the code in the correct place and it compiled it will now play small audio files. We've accomplished something similar to what the PlaySound function does. Try playing with this. Change the playback sample rate (in main) or the sample size (multiple of 8 btw) and see what happens, or even the number of channels. You'll find that changing the sample rate or number of channels speeds up or slows down the audio. Changing the sample size has a somewhat devastating affect!

Streaming Audio to the Device

As you can probably see the above code has a number of fundamental flaws (note that this was deliberate :), the most evident of which are:

  • We can't play very large files due to the way they are loaded. The current method buffers the entire file and plays it all back at once. Audio by its very nature is large so we need to find a way of streaming the data to the device block by block.
  • The current version of writeAudioBlock is synchronous so writing multiple blocks bit by bit will cause a gap between each block output (we can't refill the buffer fast enough). Microsoft recommends at least a double buffering scheme so that you fill one block while another is playing and then switch the blocks. This itself is not nearly enough. Even switching the blocks will cause a very small (but annoying) gap in the output.
Fortunately reading in blocks is a very easy exercise so I will defer from writing the code for that right now. Rather, I will concentrate on a buffering scheme for writing audio to the device in a gapless stream.

This problem of block switching is not nearly as serious as it sounds. No you can't switch two blocks without a gap but the interface does something which allows you to get around this. It maintains a queue of blocks. Any block which you have passed through the waveOutPrepareHeader function can be inserted into the queue using waveOutWrite. This means we can write 2 (or more) blocks to the device and fill a third while the first is playing, then perform the switch while the second is playing. This gives us gapless output.

The final problem before I describe a method of doing this is, how do we know when a block has finished playing? I was doing something very bad in the first version of writeAudioBlock and polling the device using waveOutUnprepareHeader until the block had finished. We can't do this any more because we need the time to refill audio blocks, and there are much better ways offered by the waveOut interface.

The waveOut interface offers 4 types of callback mechanism to notify you of when blocks have finished playing. These are:
  • An event - an event is set when a block completes
  • A callback function - a function is called when a block completes
  • A thread - a thread message is sent when a block completes
  • A window - a window message is sent when a block completes
The way you specify which of these is used is in the dwCallback parameter of the waveOutOpen function. In my method we will be using a function as the callback.

So we need a new function: waveOutProc. This (user defined) function is actually documented so you can look that up now. As you can see the function is called for three things: When the device is opened, closed, and when a block finishes. We are only interested in the call for when a block finishes.

The Buffering Scheme

My buffering scheme works on a principle similar to that discussed above. It requires the use of a variable that keeps count of the number of free buffers at any time (yes a semaphore would be ideal here but we can't use one, I'll explain why later). This variable is initialised to the number of blocks, decremented when a block is written and incremented when a block completes. When no blocks are available we wait until the counter is at least 1 and then continue writing. This allows us to queue any number of blocks in a ring which is very effective. Rather than queuing 3 blocks, I queue more like 20, of about 8 kB each.

Now here's something you might have already guessed: waveOutProc is called from a different thread. Windows create a thread specifically for managing the audio playback. There are a number of restrictions on what you can do in this callback. To quote the Microsoft Documentation:

 "Applications should not call any system-defined functions from 
inside a callback function, except for EnterCriticalSection, 
LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, 
OutputDebugString, PostMessage, PostThreadMessage, SetEvent, 
timeGetSystemTime, timeGetTime, timeKillEvent, and timeSetEvent. 
Calling other wave functions will cause deadlock."
Which explains why we can't use a semaphore - it would require the use of ReleaseSemaphore which you shouldn't use. In practice it is a little more flexible than this - I have seen code that uses semaphores from the callback but what works on one Windows version may not work on another. Also, calling waveOut functions from the callback does cause deadlock. Ideally we would also call waveOutUnprepareHeader in the callback but we can't do that (it doesn't deadlock until you call waveOutReset just for your information :)

You'll notice that waveOutOpen provides a method of passing instance data (a user defined pointer) to the callback function. We're going to use this to pass a pointer to our counter variable.

One more thing before we write the waveOutProc function by the way. Since waveOutProc is called from a different thread, two threads will end up writing to the block counter variable. To avoid any conflict we need to use a Critical Section object (which will be a static module variable called waveCriticalSection).

Here is the waveOutProc function:

static void CALLBACK waveOutProc(
HWAVEOUT hWaveOut, 
UINT uMsg, 
DWORD dwInstance, 
DWORD dwParam1,
DWORD dwParam2 
)
{
/*
 * pointer to free block counter
 */
int* freeBlockCounter = (int*)dwInstance;
/*
 * ignore calls that occur due to openining and closing the
 * device.
 */
if(uMsg != WOM_DONE)
return;
EnterCriticalSection(&waveCriticalSection);
(*freeBlockCounter)++;
LeaveCriticalSection(&waveCriticalSection);
}
 

The next thing we need is a couple of functions for allocating and freeing the block memory and a new implementation of writeAudioBlock called writeAudio. Here are the functions allocateBlocks and freeBlocks. allocateBlocks allocates a set number of blocks, with headers at a given size, and freeBlocks frees this memory. allocateBlocks will cause the program to exit if it fails. This means we don't need to check its return value in main.

WAVEHDR* allocateBlocks(int size, int count)
{
unsigned char* buffer;
int i;
WAVEHDR* blocks;
DWORD totalBufferSize = (size + sizeof(WAVEHDR)) * count;
/*
 * allocate memory for the entire set in one go
 */
if((buffer = HeapAlloc(
GetProcessHeap(), 
HEAP_ZERO_MEMORY, 
totalBufferSize
)) == NULL) {
fprintf(stderr, "Memory allocation error\n");
ExitProcess(1);
}
/*
 * and set up the pointers to each bit
 */
blocks = (WAVEHDR*)buffer;
buffer += sizeof(WAVEHDR) * count;
for(i = 0; i < count; i++) {
blocks[i].dwBufferLength = size;
blocks[i].lpData = buffer;
buffer += size;
}
return blocks;
}
void freeBlocks(WAVEHDR* blockArray)
{
/* 
 * and this is why allocateBlocks works the way it does
 */ 
HeapFree(GetProcessHeap(), 0, blockArray);
}
 

The new function writeAudio needs to queue as many blocks as necessary to write the data. The basic algorithm is:

While there's data available
If the current free block is prepared
Unprepare it
End If
If there's space in the current free block
		Write all the data to the block
Exit the function
Else
Write as much data as is possible to fill the block
Prepare the block
Write it
Decrement the free blocks counter
Subtract however many bytes were written from the data available
Wait for at least one block to become free
Update the current block pointer
End If
End While
 
This raises a question: How do I tell when a block is prepared and when it isn't?
This is a fairly easy one actually. Windows makes use of the dwFlags member of the WAVEHDR structure. It is used for a few things but one thing waveOutPrepareHeader does is set the WHDR_PREPARED flag. All we have to do is test for the flag in the dwFlags member.

I will make use of the dwUser member of the WAVEHDR structure to maintain a count of how full a block is. Here is the listing for the writeAudio function:

void writeAudio(HWAVEOUT hWaveOut, LPSTR data, int size)
{
WAVEHDR* current;
int remain;
current = &waveBlocks[waveCurrentBlock];
while(size > 0) {
/* 
 * first make sure the header we're going to use is unprepared
 */
if(current->dwFlags & WHDR_PREPARED) 
waveOutUnprepareHeader(hWaveOut, current, sizeof(WAVEHDR));
if(size < (int)(BLOCK_SIZE - current->dwUser)) {
memcpy(current->lpData + current->dwUser, data, size);
current->dwUser += size;
break;
}
remain = BLOCK_SIZE - current->dwUser;
memcpy(current->lpData + current->dwUser, data, remain);
size -= remain;
data += remain;
current->dwBufferLength = BLOCK_SIZE;
waveOutPrepareHeader(hWaveOut, current, sizeof(WAVEHDR));
waveOutWrite(hWaveOut, current, sizeof(WAVEHDR));
EnterCriticalSection(&waveCriticalSection);
waveFreeBlockCount--;
LeaveCriticalSection(&waveCriticalSection);
/*
 * wait for a block to become free
 */
while(!waveFreeBlockCount)
Sleep(10);
/*
 * point to the next block
 */
waveCurrentBlock++;
waveCurrentBlock %= BLOCK_COUNT;
current = &waveBlocks[waveCurrentBlock];
current->dwUser = 0;
}
}
 

Now we have this new function for writing the audio you can scrap the writeAudioBlock function since it's not being used any more. You can also scrap the loadAudioBlock function because the next section will start a new implementation of main that doesn't require loadAudioBlock.

The Driver Program

If you've followed this tutorial right though you will now have a C file containing the following functions:

  • main
  • waveOutProc
  • allocateBlocks
  • freeBlocks
  • writeAudio
Note that this file won't compile until we strip off the old version of main and declare the module variables needed.

We're now going to write a completely new version of main that will stream files from disk to the waveOut device. This listing also contains the declarations for the module variables and the prototypes for the functions we've already written.

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
/*
 * some good values for block size and count
 */
#define BLOCK_SIZE 8192
#define BLOCK_COUNT 20
/*
 * function prototypes
 */ 
static void CALLBACK waveOutProc(HWAVEOUT, UINT, DWORD, DWORD, DWORD);
static WAVEHDR* allocateBlocks(int size, int count);
static void freeBlocks(WAVEHDR* blockArray);
static void writeAudio(HWAVEOUT hWaveOut, LPSTR data, int size);
/*
 * module level variables
 */
static CRITICAL_SECTION waveCriticalSection;
static WAVEHDR* waveBlocks;
static volatile int waveFreeBlockCount;
static int waveCurrentBlock;
int main(int argc, char* argv[])
{
HWAVEOUT hWaveOut; /* device handle */
HANDLEhFile;/* file handle */
WAVEFORMATEX wfx; /* look this up in your documentation */
char buffer[1024]; /* intermediate buffer for reading */
int i;
/*
 * quick argument check
 */
if(argc != 2) {
fprintf(stderr, "usage: %s <filename>\n", argv[0]);
ExitProcess(1);
}
/*
 * initialise the module variables
 */ 
waveBlocks = allocateBlocks(BLOCK_SIZE, BLOCK_COUNT);
waveFreeBlockCount = BLOCK_COUNT;
waveCurrentBlock= 0;
InitializeCriticalSection(&waveCriticalSection);
/*
 * try and open the file
 */ 
if((hFile = CreateFile(
argv[1],
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
)) == INVALID_HANDLE_VALUE) {
fprintf(stderr, "%s: unable to open file '%s'\n", argv[0], argv[1]);
ExitProcess(1);
}
/*
 * set up the WAVEFORMATEX structure.
 */
wfx.nSamplesPerSec = 44100; /* sample rate */
wfx.wBitsPerSample = 16; /* sample size */
wfx.nChannels= 2; /* channels*/
wfx.cbSize = 0; /* size of _extra_ info */
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample * wfx.nChannels) >> 3;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
/*
 * try to open the default wave device. WAVE_MAPPER is
 * a constant defined in mmsystem.h, it always points to the
 * default wave device on the system (some people have 2 or
 * more sound cards).
 */
if(waveOutOpen(
&hWaveOut, 
WAVE_MAPPER, 
&wfx, 
(DWORD_PTR)waveOutProc, 
(DWORD_PTR)&waveFreeBlockCount, 
CALLBACK_FUNCTION
) != MMSYSERR_NOERROR) {
fprintf(stderr, "%s: unable to open wave mapper device\n", argv[0]);
ExitProcess(1);
}
/*
 * playback loop
 */
while(1) {
DWORD readBytes;
if(!ReadFile(hFile, buffer, sizeof(buffer), &readBytes, NULL))
break;
if(readBytes == 0)
break;
if(readBytes < sizeof(buffer)) {
printf("at end of buffer\n");
memset(buffer + readBytes, 0, sizeof(buffer) - readBytes);
printf("after memcpy\n");
}
writeAudio(hWaveOut, buffer, sizeof(buffer));
}
/*
 * wait for all blocks to complete
 */
while(waveFreeBlockCount < BLOCK_COUNT)
Sleep(10);
/*
 * unprepare any blocks that are still prepared
 */
for(i = 0; i < waveFreeBlockCount; i++) 
if(waveBlocks[i].dwFlags & WHDR_PREPARED)
waveOutUnprepareHeader(hWaveOut, &waveBlocks[i], sizeof(WAVEHDR));
DeleteCriticalSection(&waveCriticalSection);
freeBlocks(waveBlocks);
waveOutClose(hWaveOut);
CloseHandle(hFile);
return 0;
}
 

What Next?

What you do now is up to you. I have a few possibly entertaining suggestions:

  • Try modifying the rawaudio program so that it reads from standard input. This would make an application that you can directly pipe audio into from the command line.
  • Rework the reader so that it reads Wave (*.wav) files as opposed to RAW files. You will find this surprisingly easy, wave files contain a WAVEFORMATEX structure to describe their format which you can use when opening the device. See wotsit's format (http://www.wotsit.org) for information on the wave file format.
  • See if you can come up with any new or better buffering schemes
  • Try attaching this code to an open source decoder such as the Vorbis decoder or an MP3 decoder that you can acquire the source to. You then have your the beginnings of your own media player.
You can see get my example winamp plug-in from http://www.insomniavisions.com/software and try that. It is also open source.

Contacting Me

You can contact me (David Overton) by the usual methods available on Planet Source Code.

You can also go to http://www.insomniavisions.com/feedback to send feedback from my Website.

A complete working example of the code on this page can be downloaded here.


Other 6 submission(s) by this author

 


Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Intermediate category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments

7/24/2002 6:11:50 PMWaveOut

Very good tutorial. It is exactly what I was recently looking for.
(If this comment was disrespectful, please report it.)

 
7/25/2002 5:55:00 AMJared Bruni

very well written tutorial :) good job hope to see more tutorials from you in the future
(If this comment was disrespectful, please report it.)

 
7/26/2002 7:13:12 PMSparkster

very concise, my friend. I enjoyed reading this and learned somthing new in the process.
(If this comment was disrespectful, please report it.)

 
11/20/2002 3:38:45 PMAdeel Ahmad

Gr8 Job dude,
(If this comment was disrespectful, please report it.)

 
11/25/2002 7:25:33 PM

This tutorial has helped me a lot in understanding the Multimedia WaveOut conceptually. This tutorial is definitely useful for a starter.

Best wishes.

(If this comment was disrespectful, please report it.)

 
12/25/2002 2:06:42 AMJakob Bieling

Very nice! Just got my waveIn stuff working and wanted to start the waveOut thingies. This is definitely of great help! Thanks!
(If this comment was disrespectful, please report it.)

 
5/15/2003 11:23:04 AMbmat

Very good tutorial. This was exactly what I was looking for! I needed to replace the PlaySound fonction because it is not good for real-time applications.
(If this comment was disrespectful, please report it.)

 
10/5/2003 8:04:53 AM

Very good piece of work. I was actually looking for a tuturial on wave Out functions and I must say that ur's is the most understanding I got till now.
KEEP IT UP dude!
(If this comment was disrespectful, please report it.)

 
5/2/2004 10:34:27 AMAidman

excellent article!
(If this comment was disrespectful, please report it.)

 
5/9/2004 11:49:11 AM

Sir i require Wavein proceudre guidelines and also if u can tell me how can i change the format of recorded file using ACM to mp3 or some other format for size reducation.
i ll be waiting for ur response

(If this comment was disrespectful, please report it.)

 
6/30/2004 2:39:21 AM

Most excellent!

How about graphical output. Displaying wave in window?

Would apreciate to read what you have to say about that!

Also the compression things. The next logical step.
(If this comment was disrespectful, please report it.)

 
8/26/2004 1:58:43 AM

very good! Excelent!
help us most!
thanks the author.


(If this comment was disrespectful, please report it.)

 
3/15/2005 4:07:59 PM

This is the best articel I have ever seen, it helped me a lot when i built my own mediaplayer.
(If this comment was disrespectful, please report it.)

 
9/27/2005 1:12:48 PMYuriy

I am trying to use this as a sample for my OGG decoder/player. Overalll, it's good, but I think I found a problem. Sometimes it chips away (does not play) the end of the file. It probably is due to how the writeAudio() function is written: suppose, you have the very last 100 bytes to play-
you send them to writeAudio, but because these 100 bytes don't fill the current block, it goes to break command, and exits the function (and since these were the last PCM bytes, they are never played!). I was able to fix it by sending "extra" empty bytes to the writeAudio() function at the end of the music file, but I guess I'll have to invent a better fix.
writeAudio
(If this comment was disrespectful, please report it.)

 
9/28/2005 5:53:44 PMrost

as an answer to yuriy:
the easy way out (that would be considered qnd as your current solution might be) would be to make a function writeAudioFinal(...) which would be called when you know that you have no more data to stream. the function would then just flush the remaining data to the audio using waveOutWrite...

otherwise, indeed a very nice tutorial!
(If this comment was disrespectful, please report it.)

 
10/2/2005 4:13:47 PMTricia

Very good tutorial and it was very helpful but i have 2 errors when i try to compile it. They are both in the allocateBlocks() function. Hopefully i can talk to you thru email and figure out the errors. thanks
(If this comment was disrespectful, please report it.)

 
4/6/2006 3:42:35 PMPaul

Very good tutorial. Thank you.
(If this comment was disrespectful, please report it.)

 
6/5/2006 9:18:09 AMShalabh

Excellent tutorial, thank you for such a helpful tutorial
(If this comment was disrespectful, please report it.)

 
6/18/2006 11:30:28 PMHung

Thanks. I have implement your source code in my small project. I used a BOOL var to control the playing loop as below:

while( play1->m_bPlaying ) {
DWORD readBytes;

if(!ReadFile(play1->hFile, play1->buffer, sizeof(play1->buffer), &readBytes, NULL))
break; //out of this loop automatically
if(readBytes == 0)
break; //out of this loop automatically
...
}

But I found that it is slow to stop playing. When user press STOP button, he will have to wait.
The question I want to ask is that: Is there any other way to stop current playing progress with the minimum wait time???
Please help me!
Thanks.
(If this comment was disrespectful, please report it.)

 
8/15/2006 11:57:29 AMDapeng Xiong

David,

I have a question related to the waveOutWrite function. Under certain circustances, it takes 30ms or longer to play a packet of 20ms.

It is a project related to G.729 Codec of VOIP. It receives the packets of 20ms voice segments. After we decode them into PCM, we put it into a queue with three buffers. Then use waveOutWrite to play out these packets just similar as your codes above. However, it takes more than 20ms, say, 25ms and 30ms, for waveOutWrite to finish one packet. So it will cause accumulated big delay. This problem only happens for some PC sometimes. Any idea?

Your help will be very nuch appreciated.

The size of the buffer queue I am using is three. It should be good enough to realize the zero gap between packets.
(If this comment was disrespectful, please report it.)

 
8/17/2006 1:55:07 PMColin

blocks[i].lpData = buffer;

error C2440: '=' : cannot convert from 'unsigned char *' to 'LPSTR'

(If this comment was disrespectful, please report it.)

 
9/19/2006 4:23:43 PMbio4ema

excellent tutorial, thanks !!

but like someone else mentioned above, i also get two C2440 errors in the allocateBlocks() function, both connected with the 'buffer' variable.

would be great to fix them per mail.

thanks again.
(If this comment was disrespectful, please report it.)

 
10/14/2006 7:49:22 AMjaCaballero

Hello to all. I need help. Someone might help me saying to me since like it has changed the format RAW to the format WAV? I am trying to do a pipeline between two PCs by means of wavein and waveout. To do a constant streaming of information without using intermediate files (wavs,raws,...). If someone has idea of like doing it and wanted to help me it is possible to contact with me in caballero.teleco@hotmail.com. I am employed at a modem DMT software for transmission of files between two PCs. We might change opinions. Thank you very much.
(If this comment was disrespectful, please report it.)

 
11/1/2006 11:18:10 PMmun

How to play wave file on modem. I am trying to initialise modem using At commands and then i am trying to play greeting(wave file ) on modem. The problem is i am writing AT commands to COM port using handle(created by Creatfile). how can i play a wav file using this handle and if i create handle from waveoutopen it might open soundcard hanle not the modem handle on which i want to play the wavefile.How to play wave file on modem using low level API
(If this comment was disrespectful, please report it.)

 
1/12/2007 6:25:37 PMEd

I can't thank you enough. You saved me hours, maybe days, of digging through incomprehensible Microsoft documentation.

I noticed that my application tends to stutter if the buffer size is not an even power of 2. Took me some time to figure that one out. But that is a VERY MINOR QUIBBLE! The article was excellent. Thanks again.

(If this comment was disrespectful, please report it.)

 
7/21/2007 2:00:23 PMAllstar

Amazing piece of code. It helped me very much in my project. Of course, I will put your name in credits.
Thanks a lot!
(If this comment was disrespectful, please report it.)

 
10/31/2007 12:24:54 AMlinhnm

who can tell me about special play sound
(If this comment was disrespectful, please report it.)

 
1/12/2008 2:47:41 AMRekha Mekin

Really good and easy to understand.
(If this comment was disrespectful, please report it.)

 
1/12/2008 2:57:09 AMRekha Mekin

It is really very good .easy to understand.
(If this comment was disrespectful, please report it.)

 
3/29/2009 8:01:19 AMShimon

Hi
Thanks for greate tutorial!
I have 2 questions:
1. How can I perform Stop playing without delay?
2. How can I perform Pause at a certain point? and then continue paly from that point on?

Thanks
Shimon
(If this comment was disrespectful, please report it.)

 
8/2/2009 2:42:47 PMVic

The tutorial looks excellent -- and just what I was looking for. But the zip download link does not seem to work. If your issue, I would appreciate a corrected link .... But it could very well be my connection here.
(If this comment was disrespectful, please report it.)

 
11/15/2009 12:59:35 PMJukka

This was just what i was looking for.
Great!
(If this comment was disrespectful, please report it.)

 
1/20/2010 7:39:11 PMGower Cox

This is an excellent and interesting article.
(If this comment was disrespectful, please report it.)

 
2/6/2010 2:26:40 PMDoug Cox

It's an excellent article. Very well written and excelent code. However, David Overton wrote it a long time ago. These days, if you don't need to play a HUGH WAVE file, you don't need to send the audio data in blocks -- you can send it all at once. Just a few lines of code will do the trick. See my PianoRollComposer.c at http://jdmcox.com/

(If this comment was disrespectful, please report it.)

 
5/6/2010 6:12:22 PMDoug Cox

I have to amend my previous comment. I've added a SoundFont synthesizer routine to my PianoRollComposer program, and it needs to send music data in blocks because blocks are necessary in order to end a MIDI musical note gracefully by reducing the volume of the last block sent to 0. I used CALLBACK_EVENT in waveOutOpen instead of CALLBACK_FUNCTION. Once again, see my C code (SynthThread).
(If this comment was disrespectful, please report it.)

 
7/5/2011 5:55:13 AMMaxime

Hi,
this is such a good article.
I need to download the project but i think the link is dead.
How can i do to get it?
Thank you.
(If this comment was disrespectful, please report it.)

 
11/18/2011 7:05:54 AMMike

Excellent article, very clear and concise. (extremely helpful for someone with my limited experience).
Just a bit of advice for those of us who have had trouble with C2440 errors in the allocateBlocks() function on compiling in "Visual C++" (as I did).

Change the line.

if((buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, totalBufferSize)) == NULL)

to read (casting into different type).

if((buffer = (unsigned char*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, totalBufferSize)) == NULL)

and the line

blocks[i].lpData = buffer;

to read (also casting different type).

blocks[i].lpData = (LPSTR) buffer;

and it should compile without error.
(If this comment was disrespectful, please report it.)

 
7/5/2012 6:43:59 AM

Hi, how to start running the second version of the program is compiled without problems, but no sound, or am I doing something wrong.
(If this comment was disrespectful, please report it.)

 
10/10/2012 5:40:33 AMCarl

Thanks that was really useful.
I did however need to set "wfx.cbSize = sizeof(WAVEFORMATEX);" rather than 0 to get it running on windows 7. (waveOutOpen will return WAVERR_BADFORMAT with it set to 0. Works fine of set to 0 on XP though)
(If this comment was disrespectful, please report it.)

 
9/2/2014 10:53:00 PMThai

Thanks that was useful with me:)
(If this comment was disrespectful, please report it.)

 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)
 

To post feedback, first please login.