Virtual Developer Workshop: Containerized Development with Docker
Here, I will introduce an easy-to-use wave IO library. It can be used to play existing PCM encoded wave files or record sound. I am trying my best to make my explaination clear and I hope you find this library helpful. I used some lines of Alexander Beletsky's code to read wave files. A lot of thanks here.
1. Library Overview
There are four classes in this library that can be used directly in your program:
- CWaveReader: wave file reader. It is used by CWavePlayer;
- CWavePlayer: wave file player;
- CWaveWriter: wave file writer. It is used by CWaveRecorder;
- CWaveRecorder: the sound recorder.
To play a wave file, you need to:
- Use CWaveReader to open the wave file. For example:
- Create a new CWavePlayer object. For example:
- Associate the player with the wave reader created in Step 1. The code is:
- Ask the player to start. For example:
CWaveReader * reader = new CWaveReader("c:\sample\scream.wav");
CWavePlayer player = new CWavePlayer();
Then, you can call the Pause(), Resume(), FastForward(), FastReverse(), Reset() functions provided by the CWavePlayer to do various things on the player.
To record sound, the steps are just the same:
- Use CWaveWriter to careat a new wave file. For example:
- Create a new CWaveRecorder object. For example:
- Associate the recorder with the wave writer created in Step 1. The code is:
- Ask the recorder to start. For example:
CWaveWriter * writer = new CWaveWriter ("c:\sample\new_scream.wav");
CWaveRecorder player = new CWaveRecorder ();
The only difference is that the recorder does not provide FastForward and FastReverse functions.
2. Library Anatomy
The most important class in this library is CWaveIOBase. It is the base class of CWavePlayer and CWaveRecorder.
This class maintains three queues. One queue contains wave data (called 'ready wave headers') waiting to be sent to the wave device; another queue maintains wave data (called 'in use headers') that have been sent to the wave device; the last one contains empty buffers (called 'free headers') waiting to be filled with wave data.
The CWaveIOBase creates two threads to manage these three queues (see Figure 1).
Thread 1 manages the "free headers" and "in use headers" queues. If there is any element in the "free headers" queue, this thread takes it out and calls the virtual function PrepareFreeHeader(...), which is to be implemented by CWavePlayer and CWaveRecorder, to have it filled with wave data. Then, this thread sends the element to the "ready wave headers" queue.
The first thread also checks the "In Use Headers" queue to see whether there are any elements that have been consumed by the wave device. If there are, this thread takes them out, calls the virtual function FreeReturnedHeader(...) to free them, and at last sends them to the "Free Headers" queue.
Thread 2 manages the "Ready Headers" queue. If there are any elements in this queue, this thread calls the virtual function SendToSoundDevice(...) to send them to the wave device.
Another important class in this library is CWaveDataStore, the base class of CWaveReader and CWaveWriter. CWaveDataStore provides interfaces for wave data reading (used by player) or writing (recorder).
CWaveDataStore declares four virual member functions (see Figure 2).
The StoreData(...) function is implemented by the wave writer to save recorded wave data; the wave reader implements the FetchData(...) function to read wave data from, for example, a wave file. The GetWavePara(...) virtual function tells a wave player (or wave recorder) the properties of the wave data to play (or record); for example, the sample rate, number of channels, and bits per sample. The last virtual function, Seek(...), moves reading or writing wave data pointers.
3. Player/Recorder Operations
The following operations are provided by CWavePlayer:
- Open(): Associate the player with a wave reader;
- Start(): You must call Start() to begin playing a wave file after Open();
- Pause(): Pause playing a wave file. Call Resume() to continue playing. (Calling Start() will have no effect.);
- Resume(): Resume playing a paused wave file;
- Reset(): Reset the wave file to its beginning and stop playing. Call Start() to continue playing. (Calling Resume() will have no effect.);
- FastForward(): Fast forward the wave file and stop playing. Call Start() to continue playing. (Calling Resume() will have no effect.);
- FastReverse(): Fast reverse the wave file and stop playing. Call Start() to continue playing. (Calling Resume() will have no effect.);
- Close(): De-associate the wave reader with the player. It is your own responsiblity to close the wave reader after calling Close() on the player. The player does not close the reader for you.
- Quit(): Quit the player. All threads will exit and queues cleared, which mean the player will not work any more.
Figure 3 describes these operations (Open() and Close() not included).
The arrowed lines mean that, if you call an operation at the beginning of a line, you need to call the operation at the end of the same line to make the player continue to work. For example, if you call Start() directly after Pause(), you will hear no sound. But if you call Reset() after the Pause(), and then the Start(), the player will run.
CWaveRecorder has the same set of operations except FastForward and FastReverse. Note that calling Reset on a CWaveRecorder means all previously recorded data is lost.
The library is included in the demo project for downloading. Remember to use a multithreaded runtime library in your project.
If you find bugs or have any questions on using this library, please feel free to let me know. Thanks again for reading this article.
- CWaveFile—a Class for Working with and Representing Data from WAVEs, Alexander Beletsky.
- Playing Audio in Windows using waveOut Interface, David Overton
- Microsoft MSDN Multimedia Functions, Microsoft