Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VC9.0

A Recording and Playing Thread Class

3.73/5 (8 votes)
17 Jan 2010CPOL3 min read 41.8K   3K  
This is a class derived from CWinThread class. It is used to record and play sound in the specific thread.
rec.jpg

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.

C++
#include "NewThread.h"
...
 CNewThread *pThread;
...
 pThread=new CNewThread();
 pThread->CreateThread ();
...
//Okay, it does work now.

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.

C++
#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.

C++
class CNewThread : public CWinThread
{
 DECLARE_DYNCREATE(CNewThread)
public:
 CNewThread();           // protected constructor used by dynamic creation
// Attributes
public:
 virtual ~CNewThread();
// Operations
public:
 CString m_strState;  //Its a CString object that indicates the state of the thread.
 CString GetState();  //Get the state of the thread, which it's playing or recording...
 int GetRecSecond();  //Get tick counts of the recording by seconds.
 void StopPlay();     //Stop playing.
 void StartToPlay();  //Begin playing.
 void StopRec();      //Stop record.
 void StartToRec();   //Begin recording.
private:
 WAVEFORMATEX wf;     //It's a structure of WAVEFORMATX,
		    //will be passed into waveInOpen(...)
 HWAVEIN hWaveIn;     //The handle of the input device
 HWAVEOUT hWaveOut;   //Then handle of the output device
 WAVEHDR *waveHdr;    //The pointer of structure 
		    //will be initialized in initialization method
 BYTE *Buffer;        //A buffer to store data per once recording
 BYTE *pTotalBuf,*pBuf; //The buffer to store  the total data have been recorded.
 BOOL bRec;           //The state of recording
 UINT uLength;        //The length of recording
 // Overrides
 // ClassWizard generated virtual function overrides
 //{{AFX_VIRTUAL(CNewThread)
 public:
 virtual BOOL InitInstance();
 virtual int ExitInstance();
 virtual BOOL PreTranslateMessage(MSG* pMsg);
 //}}AFX_VIRTUAL
// Implementation
protected:
// Generated message map functions
 //{{AFX_MSG(CNewThread)
afx_msg void ON_WOMCLOSE();   //These functions are used to deal 
		//with the messages will be sent by waveInOpen and waveOutOpen.
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);
// NOTE - the ClassWizard will add and remove member functions here.
 //}}AFX_MSG
 DECLARE_MESSAGE_MAP()
private:
 UINT m_nRecTime;  //The time, has been recorded.
 void InitSound();  //Alloc memory for the buffers.
 };

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.

C++
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:

C++
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));  //Alloc memory for WAVEHDR structure.
}

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:

C++
BEGIN_MESSAGE_MAP(CNewThread, CWinThread)
 //{{AFX_MSG_MAP(CNewThread)
  // NOTE - the ClassWizard will add and remove mapping macros here.
 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)
 //}}AFX_MSG_MAP

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:

C++
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.

C++
::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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)