首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

8.3  音频接收方案

音频的接收除了实时通话以外还有音频存储和回放,在前面已经介绍了语音数据传输,本节将介绍音频存储和回放是如何实现的。

8.3.1  音频存储方案

在使用语音聊天程序时,有时需要把对方说的话录制下来,本例就是实现接收语音数据并保存的功能,接收的数据将保存为WAV文件,运行语音存储程序如图8.27 、图8.28所示。

   

图8.27  视频单屏显示(服务器端)                图8.28  视频单屏显示(客户端)

保存的文件如图8.29所示。

图8.29  保存的文件

客户端程序设计步骤如下:

*  实例位置:光盘\mr\8\8.3\8.3.1\01

(1)创建一个基于对话框的应用程序,向对话框中添加一个IP控件和2个按钮控件,对话框资源设计如图8.30所示。

图8.30  对话框设计

(2)在应用程序的InitInstance方法中初始化套接字。

WSADATA wsd;

WSAStartup(MAKEWORD(2,2),&wsd);

(3)从CSocket类中派生一个子类CClientSocket。在头文件中引用Afxsock.h头文件,目的是使用CSocket类;引用主对话框的头文件,并对主对话框进行前导声明,因为在CClientSocket类中需要定义主对话框类指针。

(4)在对话框的头文件中引入winmm.lib库,并声明如下变量。

#include "ClientSocket.h"

#include <MMSystem.h>

#pragma comment(lib, "winmm.lib")

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

/////////////////////////////////////////////////////////////////////////////

// CClientDlg dialog

class CClientSocket;

class CClientDlg : public CDialog

{

// Construction

public:

   void StartRecord();

   void InitAudioDevice();

   void ReceiveData();

   CClientDlg(CWnd* pParent = NULL);    // standard constructor

   CClientSocket* m_pClient;

   HWAVEIN         m_hWaveIn;      //录音设备

   WAVEFORMATEX    waveform;        //声音格式

   WAVEHDR         lpInWaveHdr[2]; //录音缓存

   char            lpInbuf[4096];

   char            lpInbuf1[4096];

   BOOL            m_Change;

……//此处代码省略

};

(5)在对话框中定义一个CClientSocket对象指针,创建套接字。

BOOL CClientDlg::OnInitDialog()

{

   ……//此处代码省略

   m_pClient = new CClientSocket(this);

   if(!m_pClient->Create()) //创建套接字

   {

       delete m_pClient;

       MessageBox("套接字创建失败.");

       return FALSE;

   }

   return TRUE;

}

(6)处理“连接”按钮的单击事件,连接服务器,并在连接以后开启定时器。

void CClientDlg::OnButconnect()

{

   UpdateData(TRUE);

   CString sip;

   m_IP.GetWindowText(sip); //读取服务器名称

   int port;

   port = 700; //获取端口

  

   if(! m_pClient->Connect(sip,port)) //连接服务器

   {

       MessageBox("连接服务器失败!");

       return;

   }

}

(7)向对话框中添加ReceiveData方法,用于接收服务器端传过来的连接成功信息。

void CClientDlg::ReceiveData()

{

   char buffer[2];

   //接收传来的数据

   int factdata =  m_pClient->Receive(buffer,2);

  

   buffer[factdata] = '\0';

   if(buffer[0]='~')

       MessageBox("连接成功");

}

(8)向对话框中添加InitAudioDevice方法,该方法可以初始化音频设备。

void CClientDlg::InitAudioDevice()

{

   waveform.wFormatTag       = WAVE_FORMAT_PCM ;  // 采样方式,PCM(脉冲编码调制)

    waveform.nChannels        = 2;              // 双声道

    waveform.nSamplesPerSec  = 11025;         // 采样率11.025KHz

    waveform.nAvgBytesPerSec = 11025;         // 数据率11.025KB/s

    waveform.nBlockAlign      = 1;              // 最小块单元,wBitsPerSample×nChannels/8

    waveform.wBitsPerSample  = 8;              // 样本大小为8bit

    waveform.cbSize            = 0;      

   lpInWaveHdr[0].dwBufferLength  = 4096;

   lpInWaveHdr[0].lpData            = lpInbuf;

   lpInWaveHdr[0].dwBytesRecorded = 0;

   lpInWaveHdr[0].dwFlags           = WHDR_BEGINLOOP | WHDR_ENDLOOP;

   lpInWaveHdr[0].dwLoops           = 1;

   lpInWaveHdr[0].dwUser            = 0;

   lpInWaveHdr[0].lpNext            = NULL;

   lpInWaveHdr[0].reserved          = 0;

   lpInWaveHdr[1].dwBufferLength  = 4096;

   lpInWaveHdr[1].lpData            = lpInbuf1;

   lpInWaveHdr[1].dwBytesRecorded = 0;

   lpInWaveHdr[1].dwFlags           = WHDR_BEGINLOOP | WHDR_ENDLOOP;

   lpInWaveHdr[1].dwLoops           = 1;

   lpInWaveHdr[1].dwUser            = 0;

   lpInWaveHdr[1].lpNext            = NULL;

   lpInWaveHdr[1].reserved          = 0;

   //打开录音设备

   waveInOpen(&m_hWaveIn,WAVE_MAPPER ,&waveform,(DWORD)m_hWnd,0,CALLBACK_WINDOW);

   for(int i=0;i<=1;i++)

       waveInPrepareHeader(m_hWaveIn,&lpInWaveHdr[i],4096);

   StartRecord();

}

(9)处理“发送”按钮的单击事件,调用InitAudioDevice方法初始化音频设备。

void CClientDlg::OnButsend()

{

   InitAudioDevice();

}

(10)向对话框中添加StartRecord方法,开始录音。

void CClientDlg::StartRecord()

{

   //准备录音缓冲区

   if(m_Change)

       waveInAddBuffer(m_hWaveIn,&lpInWaveHdr[0],sizeof(WAVEHDR));

   else

       waveInAddBuffer(m_hWaveIn,&lpInWaveHdr[1],sizeof(WAVEHDR));

   //开始录音

   waveInStart(m_hWaveIn);

}

(11)手动添加MM_WIM_DATA消息,消息处理函数为OnWinData,该消息在录音缓冲区满时触发,在该消息中将缓冲区的数据发送到服务器端。

void CClientDlg::OnWinData()

{

   if(m_Change)

       m_pClient->Send(lpInWaveHdr[0].lpData,4096);

   else

       m_pClient->Send(lpInWaveHdr[1].lpData,4096);

   m_Change = !m_Change;

   StartRecord();

}

(12)处理对话框的WM_DESTROY消息,在窗口关闭时关闭套接字和录音设备。

void CClientDlg::OnDestroy()

{

   CDialog::OnDestroy();

  

   for(int i=0;i<=1;i++)

   {

       waveInUnprepareHeader(m_hWaveIn,&lpInWaveHdr[i],4096);

   }

   if(m_pClient != NULL)

       delete m_pClient;

}

服务器端程序设计步骤如下:

*  实例位置:光盘\mr\8\8.3\8.3.1\02

(1)创建一个基于对话框的应用程序,向对话框中添加一个静态文本控件、一个群组控件和3个按钮控件,对话框设计如图8.31 所示。

图8.31  对话框设计

(2)在应用程序的InitInstance方法中初始化套接字。

   WSADATA wsd;

   WSAStartup(MAKEWORD(2,2),&wsd);

(3)从CSocket类派生新类CClientSocket和CServerSocket,在头文件中引用对话框的头文件和afxsock.h头文件,并对对话框类进行前导声明。

(4)在对话框类的头文件中引用MMSystem.h头文件,连接winmm.lib库文件并声明如下变量。

#include "ServerSocket.h"

#include "ClientSocket.h"

#include <MMSystem.h>

#pragma comment(lib, "winmm.lib")

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

/////////////////////////////////////////////////////////////////////////////

// CServerDlg dialog

class CServerSocket;

class CClientSocket;

class CServerDlg : public CDialog

{

// Construction

public:

   void ReceiveData(CClientSocket* socket);

   void AcceptConnect();

   CServerDlg(CWnd* pParent = NULL);    // standard constructor

   CServerSocket* m_pServer;

   CClientSocket* m_pClient;

   LPSTR lptr;

   HANDLE hData;

   int nBuf;

   int size;

   CString strName;

   WAVEFORMATEX    waveform;         //声音格式

   ……//此处代码省略

};

(5)绑定套接字,并开始监听客户端。

void CServerDlg::OnButlisten()

{

   if(!m_pServer->Create(700))

   {

       MessageBox("套接字创建失败");

       delete m_pServer;

       m_pServer = NULL;

       return;

   }

   if (!m_pServer->Listen())

       MessageBox("监听失败");

   hData = GlobalAlloc(GMEM_MOVEABLE, 4096);

   lptr = (char*)GlobalLock(hData);

}

(6)在对话框中添加AcceptConnect方法,用于接受客户端的连接,如果连接成功就向客户端发送确认信息。

void CServerDlg::AcceptConnect()

   if(m_pServer->Accept(*m_pClient))

   {

       char a[1];

       a[0] = '~';

       m_pClient->Send(a,1);

   }

}

(7)处理“选择”按钮的单击事件,获得要保存为文件的路径名。

void CServerDlg::OnButpath()

{

   CFileDialog dlg(false,NULL,NULL,NULL,"文件(*.wav)|*.wav||");

   if(dlg.DoModal()==IDOK)

   {

       strName = dlg.GetPathName();

       strName += ".wav";

       m_path.SetWindowText(strName);

   }

}

(8)在对话框中添加ReceiveData方法,用于接受客户端的发送的数据。

void CServerDlg::ReceiveData(CClientSocket *socket)

{

   HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE,4096);

   char* lpBuf = (char*)GlobalLock(hGlobal);

   memset(lpBuf,0,4096);

   size = socket->Receive(lpBuf,4096);

   memcpy(lptr+(nBuf-1)*4096,lpBuf,4096*sizeof(BYTE));

   GlobalUnlock(hData);

   hData = GlobalReAlloc(hData, (++nBuf)*4096, GMEM_MOVEABLE);

   lptr = (char*)GlobalLock(hData);

   GlobalUnlock(hGlobal);

   GlobalFree(hGlobal);

}

(9)处理“保存”按钮的单击事件,将接收的数据保存为WAV文件。

void CServerDlg::OnButsave()

{

   if(strName.IsEmpty())

   {

       MessageBox("请选择保存的文件名");

   }

   waveform.nChannels       = 2;

   waveform.wFormatTag      = WAVE_FORMAT_PCM;

   waveform.cbSize          = 0;

   waveform.wBitsPerSample  = 8;

   waveform.nSamplesPerSec  = 11052;

   waveform.nBlockAlign     = waveform.nChannels * (waveform.wBitsPerSample / 8);

   waveform.nAvgBytesPerSec = waveform.nSamplesPerSec * waveform.nBlockAlign;

   //保存

   HMMIO       hmmio;

    MMCKINFO    ciRiffChunk;

    MMCKINFO    ciSubChunk;

    MMIOINFO    mmioInfo;

   TCHAR file[255];

   mmioInfo.dwFlags     = 0;

    mmioInfo.fccIOProc   = mmioStringToFOURCC("WAV", 0);

    mmioInfo.pIOProc     = NULL;

    mmioInfo.wErrorRet   = 0;

    mmioInfo.htask       = 0;

    mmioInfo.cchBuffer   = 0;

    mmioInfo.pchBuffer   = 0;

    mmioInfo.pchNext     = 0;

    mmioInfo.pchEndRead  = 0;

    mmioInfo.pchEndWrite = 0;

    mmioInfo.lBufOffset  = 0;

    mmioInfo.lDiskOffset = 0;

    mmioInfo.adwInfo[0]  = 0;

    mmioInfo.adwInfo[1]  = 0;

    mmioInfo.adwInfo[2]  = 0;

    mmioInfo.adwInfo[3]  = 0;

    mmioInfo.dwReserved1 = 0;

    mmioInfo.dwReserved2 = 0;

    mmioInfo.hmmio       = 0;

  

   wsprintf(file,_T("%s"),strName);

   hmmio = mmioOpen(file, &mmioInfo,

                     MMIO_CREATE | MMIO_WRITE | MMIO_ALLOCBUF);

   mmioSeek(hmmio, 0, SEEK_SET);

    ciRiffChunk.fccType = mmioFOURCC('W', 'A', 'V', 'E');

    ciRiffChunk.cksize  = 0L;

   

   mmioCreateChunk(hmmio, &ciRiffChunk, MMIO_CREATERIFF);

   ciSubChunk.ckid   = mmioStringToFOURCC("fmt", 0);

    ciSubChunk.cksize = sizeof(WAVEFORMATEX)-2;

   

   mmioCreateChunk(hmmio, &ciSubChunk, 0);

    mmioWrite(hmmio, (HPSTR)&waveform, sizeof(WAVEFORMATEX));

   mmioAscend(hmmio, &ciSubChunk, 0);

   ciSubChunk.ckid   = mmioStringToFOURCC("data", 0);

    ciSubChunk.cksize = (nBuf-1)*4096+size;

   mmioCreateChunk(hmmio, &ciSubChunk, 0);

   mmioWrite(hmmio, (HPSTR)lptr,(LONG)(nBuf-1)*4096+size);

   mmioAscend(hmmio, &ciSubChunk, 0);

   mmioAscend(hmmio, &ciRiffChunk, 0);

   mmioFlush(hmmio, 0);

    mmioClose(hmmio, 0);

  

   MessageBox("已保存");

}

(10)处理对话框的WM_DESTROY消息,在窗口关闭时关闭套接字。

void CServerDlg::OnDestroy()

{

   CDialog::OnDestroy();

  

   GlobalUnlock(hData);

   GlobalFree(hData);

   if(m_pServer != NULL)

       delete m_pServer;

   if(m_pClient != NULL)

       delete m_pClient;

}

8.3.2  音频回放设计方案

存储声音时通常都是保存成WAV文件,WAV是一种常见的音频文件格式,可以使用MCI指令播放WAV文件,MCI指令是Windows系统中多媒体硬件的接口,通过该指令还可以实现播放CD、控制光驱、控制音量等操作。

播放wav文件的步骤如下:

(1)向工程中导入winmm.lib库。

(2)定义MCIDEVICEID变量,用来设置设备ID号,定义MCI_OPEN_PARMS变量,用来设置打开设备类型,定义MCI_PLAY_PARMS变量,用来设置播放回调函数。

(3)使用mciSendCommand函数使用MCI_OPEN命令打开播放wav文件的设备,获得mciOpenParms变量的wDeviceID值。

(4)通过MCI_OPEN_PARMS变量设置lpstrElementName属性,该属性保存的是wav文件的路径,并通过mciSendCommand函数的将文件打开。

(5)使用mciSendCommand函数的MCI_PLAY命令播放文件。

音频回放程序运行如图8.32 所示。

图8.32  视频回放

音频回放程序设计步骤如下:

*  实例位置:光盘\mr\8\8.3\8.3.2\01

(1)创建一个基于对话框的应用程序,向对话框中添加一个静态文本控件和2个按钮控件,对话框设计如图8.33所示。

图8.33  对话框设计

(2)在StdAfx.h文件中导入winmm.lib库。

#include <MMSystem.h>

#pragma comment(lib, "winmm.lib")

(3)处理“选择文件”按钮的单击事件,用来选择WAV文件。

void CWAVPlayDlg::OnOpen()

{

   CFileDialog dlg(TRUE,"文件","*.wav",OFN_HIDEREADONLY,

       "声音文件(*.wav)|*.wav||",NULL);

   if(dlg.DoModal()==IDOK)

   {

        strName = dlg.GetPathName();

       m_Path.SetWindowText(strName);

   }

}

(4)处理“播放”按钮的单击事件,用来播放选择的WAV文件。

void CWAVPlayDlg::OnPlay()

{

   MCIDEVICEID m_nDeviceID;

   MCIDEVICEID m_nElementID;

   MCI_OPEN_PARMS mciOpenParms;

   mciOpenParms.lpstrDeviceType=(LPSTR)MCI_DEVTYPE_WAVEFORM_AUDIO;

   mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID

       | MCI_WAIT,(DWORD)(LPVOID)&mciOpenParms);

   m_nDeviceID=mciOpenParms.wDeviceID;

   MCI_OPEN_PARMS mciOpen;

   memset(&mciOpen,0,sizeof(MCI_OPEN_PARMS));

   mciOpen.lpstrElementName = strName;

   mciSendCommand(m_nDeviceID,MCI_OPEN,MCI_OPEN_ELEMENT,(DWORD)(LPVOID)&mciOpen);

   m_nElementID=mciOpen.wDeviceID;

   MCI_PLAY_PARMS mciPlay;

   mciPlay.dwCallback=(DWORD)this->GetSafeHwnd();

   mciSendCommand(m_nElementID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID)&mciPlay);

}

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论