65.9K
CodeProject 正在变化。 阅读更多。
Home

录音和播放线程类

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.73/5 (8投票s)

2010年1月17日

CPOL

3分钟阅读

viewsIcon

42391

downloadIcon

3101

这是一个派生自 CWinThread 类的类。它用于在特定线程中录制和播放声音。

rec.jpg

简介与背景

该演示展示了一种使用线程录制和播放声音的方法。我创建了一个派生自 CWinThread 的类,它用于在线程中播放和录制声音。 这是一个可重用的类,因此您可以在您的项目中使用它。 我在 MFC 上构建了它,所以我不确定它在其他条件下是否能很好地工作。 它可能有一些错误,我还没有找到解决它们的方法。 我编写这个类的目的是因为我想帮助您简化您的繁重工作。

Using the Code

使用代码很简单,只需遵循几个步骤

首先,将两个文件添加到您的项目中:“NewThread.h”和“NewThread.cpp”。 请先在文章顶部下载它。

其次,在代码的适当位置声明对象。

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

我将在下面详细讨论如何创建该类。

类的原理

当我想录制和播放声音时,首先必须解决的问题。 我应该选择什么 API。 有时,我们使用 sndPlaySound() 函数播放声音文件或使用 MCI 函数。 但这一次,我使用 waveInOpen()waveOutOpen()。 所以,第一件事是检查它的头文件和库,并将其添加到这个类的头文件中。

#include "mmsystem.h"
#pragma comment(lib,"winmm.lib")

好的,让我们继续讨论这个类,从主菜单点击“插入”,插入一个派生自 MFC CWinThread 的新类。 将以上代码片段添加到类的头文件中。

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.
 };

这些代码片段是由 ClassWizard 创建的,所以不要全部手动输入。 右键单击您刚刚插入的新类,添加函数和成员变量是我们所需要的。

waveInOpen

waveInOpen 函数打开给定的波形音频输入设备以进行录音。

MMRESULT waveInOpen(
  LPHWAVEIN phwi,            
  UINT uDeviceID,            
  LPWAVEFORMATEX pwfx,       
  DWORD dwCallback,          
  DWORD dwCallbackInstance,  
  DWORD fdwOpen              
);

从 MSDN 我们得知,waveInOpen 需要 WAVEFORMATX 结构的地址,以便我们可以在初始化函数中创建它

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.
}

从代码片段中,我们还发现它已经为缓冲区 “waveHdr” 分配了内存。

再次查看 “waveInOpen” 函数,关注最后一个参数,它非常重要。

它决定了我们应该使用哪种回调机制。 我们需要选择三个:CALLBACK_FUNCTIONCALLBACK_THREADCALLBACK_WINDOW,对于这个类,我选择 CALLBACK_THREAD,这意味着我将在线程中处理它的消息。 如果您选择 CALLBACK_WINDOW,我们应该使用 ON_MESSAGE(...) 宏来映射消息。 与窗口不同,当我们选择 CALLBACK_THREAD 时 “要小心”,我们应该使用宏 ON_THREAD_MESSAGE 而不是 ON_MESSAGE。 看看下面的代码片段

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

备注:ON_WIMOPEN 是类成员函数的名称。 右键单击刚刚插入到项目中的类,添加成员函数,就可以了。

如何进行连续录音? 这正是一个好问题。 看看下面的代码

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));
}

当缓冲区已满时,将发送 MM_WIM_DATA 消息,因此将调用 ON_WIMDATA() 函数。 将数据从缓冲区 “Buffer” 复制到缓冲区 “pTotalBuf”,并重新分配 buffer pTotalBuf 的内存,扩大其空间。

::waveInAddBuffer (hWaveIn,((PWAVEHDR)lParam),sizeof(WAVEHDR));

这一行将添加一个新缓冲区来填充,换句话说,录音数据将存储在添加的缓冲区中。 因此,缓冲区 “Buffer” 可以再次用于存储新数据,当再次已满时,将数据复制到总缓冲区。 播放很简单,只需播放总缓冲区 “pTotalBuf” 就行了。

关注点

对我来说,这是一个使用线程和声音进行编程的机会。 我通过编写一些代码学到了很多东西。 希望它对你有所帮助。

历史

  • 2010 年 1 月 17 日:首次发布
© . All rights reserved.