Introduction & Background
The demo presents a method for recording and playing sound with a thread. I create a class which derived from a CWinThread
, it's used to play and record sound in a thread. It's a reusable class, thus you could use it in your project. I built it on MFC, so I don't know whether it will work well in other conditions. It may have several bugs, I haven't found a way to deal with them. The purpose I wrote the class is because I want to help you to simplify your heavy work.
Using the Code
To use the code is simple, just several steps you should follow:
First, add the two files into your project: "NewThread.h" and "NewThread.cpp". Please download it first at the top of the article.
Second, declare the object in appropriate places in your code.
#include "NewThread.h"
...
CNewThread *pThread;
...
pThread=new CNewThread();
pThread->CreateThread ();
...
I'll talk more about how to create the class below.
Principle of the Class
While I want to record and play sound, the first question must be solved. What API should I select. Sometimes, we use sndPlaySound()
function to play a sound file or use MCI functions. But this time, I use waveInOpen()
and waveOutOpen()
. So, the first thing is to check out its headers and libs and add it into this class's header.
#include "mmsystem.h"
#pragma comment(lib,"winmm.lib")
Okay, let's go on with the class, click "Insert" from the main menu, insert a new class derived from the MFC CWinThread
. Add the above snippets into the class's header.
class CNewThread : public CWinThread
{
DECLARE_DYNCREATE(CNewThread)
public:
CNewThread(); public:
virtual ~CNewThread();
public:
CString m_strState; CString GetState(); int GetRecSecond(); void StopPlay(); void StartToPlay(); void StopRec(); void StartToRec(); private:
WAVEFORMATEX wf; HWAVEIN hWaveIn; HWAVEOUT hWaveOut; WAVEHDR *waveHdr; BYTE *Buffer; BYTE *pTotalBuf,*pBuf; BOOL bRec; UINT uLength; public:
virtual BOOL InitInstance();
virtual int ExitInstance();
virtual BOOL PreTranslateMessage(MSG* pMsg);
protected:
afx_msg void ON_WOMCLOSE(); afx_msg void ON_WOMDATA();
afx_msg void ON_WOMOPEN();
afx_msg void ON_WIMDATA(UINT wParam, LONG lParam);
afx_msg void ON_WIMOPEN(UINT wParam, LONG lParam);
afx_msg void ON_WIMCLOSE(UINT wParam, LONG lParam);
DECLARE_MESSAGE_MAP()
private:
UINT m_nRecTime; void InitSound(); };
The snippets were created by ClassWizard
, so do not type all of them. Right click the new class you have just inserted, add functions and member variables is all we need.
waveInOpen
The waveInOpen
function opens the given waveform-audio input device for recording.
MMRESULT waveInOpen(
LPHWAVEIN phwi,
UINT uDeviceID,
LPWAVEFORMATEX pwfx,
DWORD dwCallback,
DWORD dwCallbackInstance,
DWORD fdwOpen
);
From MSDN we got that, waveInOpen
needs an address of the WAVEFORMATX
structure so that we can create it in initialization function:
void CNewThread::InitSound()
{
bRec=false;
wf.cbSize=0;
wf.wFormatTag=WAVE_FORMAT_PCM;
wf.nChannels=1;
wf.nSamplesPerSec=11025;
wf.wBitsPerSample =8;
wf.nBlockAlign=1;
wf.nAvgBytesPerSec =11025;
waveHdr=(WAVEHDR*)malloc(sizeof(WAVEHDR)); }
From the snippets, we also found that it already has alloc memory for buffer "waveHdr
".
Look at the "waveInOpen
" function again, focus on the last parameter, it's very important.
It decides what kinds of callback mechanisms we should use. There are three we need to choose: CALLBACK_FUNCTION
, CALLBACK_THREAD
, and CALLBACK_WINDOW
, for this class I choose CALLBACK_THREAD
, it means that I'll deal with its message in the thread. If you select CALLBACK_WINDOW
, we should use ON_MESSAGE(...)
macro to map the message. Different with window, when we select CALLBACK_THREAD
"Be Careful", we should use the macro ON_THREAD_MESSAGE
instead of ON_MESSAGE
. Look at the snippets below:
BEGIN_MESSAGE_MAP(CNewThread, CWinThread)
ON_THREAD_MESSAGE(MM_WIM_OPEN,ON_WIMOPEN)
ON_THREAD_MESSAGE(MM_WIM_DATA,ON_WIMDATA)
ON_THREAD_MESSAGE(MM_WIM_CLOSE,ON_WIMCLOSE)
ON_THREAD_MESSAGE(MM_WOM_OPEN,ON_WOMOPEN)
ON_THREAD_MESSAGE(MM_WOM_DONE,ON_WOMDATA)
ON_THREAD_MESSAGE(MM_WOM_CLOSE,ON_WOMCLOSE)
Remark: ON_WIMOPEN
is the name of the class's member functions. Right click the class just insert into the project add member functions, it's ok.
How to do continuous recording? That's just a good question. Look at the code below:
void CNewThread::ON_WIMDATA(UINT wParam, LONG lParam)
{
pBuf=(BYTE*)realloc(pTotalBuf,uLength+((PWAVEHDR)lParam)->dwBytesRecorded );
pTotalBuf=pBuf;
memcpy(pTotalBuf+uLength,((PWAVEHDR)lParam)->lpData ,
((PWAVEHDR)lParam)->dwBytesRecorded);
uLength+=((PWAVEHDR)lParam)->dwBytesRecorded;
if(bRec)
::waveInAddBuffer (hWaveIn,((PWAVEHDR)lParam),sizeof(WAVEHDR));
}
When the buffer is full, the MM_WIM_DATA
message will be sent, so that the ON_WIMDATA()
function will be called. Copy data from buffer "Buffer
" to buffer "pTotalBuf
" and realloc the buffer pTotalBuf
's memory, enlarge its space.
::waveInAddBuffer (hWaveIn,((PWAVEHDR)lParam),sizeof(WAVEHDR));
This line will add a new buffer to fill, in other words, the recording data will be stored in the added buffer. So the buffer "Buffer
" can be used again to store new data when it's full again copy data to total buffer. While playing is easy, just play the total buffer "pTotalBuf
" just right.
Points of Interest
For me, it has been a chance to program with thread and sound. I have learned much via writing some code. Hope it will be useful to you.
History
- 17th January, 2010: Initial post