📄 wavecn_com 世纪音频 - 音频应用 - 基于wavex低级音频函数的实时语音通信.htm
字号:
src="WaveCN_com 世纪音频 - 音频应用 - 基于WaveX低级音频函数的实时语音通信.files/show_ads.js"
type=text/javascript></SCRIPT>
</FIELDSET>
<H1><A id=_Toc162973506 name=_Toc162973506><SPAN>2 </SPAN>背景介绍</A></H1>
<P> <SPAN>WINDOWS</SPAN>下音频的采集,播放有三种模式:</P>
<P> <SPAN>1</SPAN>)通过高级音频函数、媒体控制接口<SPAN>MCI[1</SPAN>、<SPAN>2]</SPAN>设备驱动程序;</P>
<P> <SPAN>2</SPAN>)低级音频函数<SPAN>MIDI Mapper</SPAN>、低级音频设备驱动(<SPAN>WaveX
API</SPAN>);</P>
<P> <SPAN>3</SPAN>)利用<SPAN>DirectX</SPAN>中的<SPAN>DirectSound</SPAN>;</P>
<P> 使用<SPAN>MCI</SPAN>的方法极其简便,灵活性较差;使用低级音频函数的方法相对来说难一点,但是能够对音频数据进行灵活的操控;而采用<SPAN>DirectSound</SPAN>的方法,控制声音数据灵活,效果比前二者都好,但实现起来是三者中最难的。</P>
<P> 低层音频服务及重要的数据结构低级音频服务控制着不同的音频设备,这些设备包括<SPAN>
WAVE,MIDI</SPAN>和辅助音频设备。低级音频服务包括如下内容:</P>
<P> <SPAN>(1)</SPAN>查询音频设备;</P>
<P> <SPAN>(2)</SPAN>打开和关闭设备驱动程序;</P>
<P> <SPAN>(3)</SPAN>分配和准备音频数据块;</P>
<P> <SPAN>(4)</SPAN>管理音频数据块;</P>
<P> <SPAN>(5)</SPAN>应用<SPAN>MMTIME</SPAN>结构;</P>
<P> <SPAN>(6)</SPAN>处理错误。</P>
<P> <SPAN>WaveX</SPAN>低级音频函数的相关声明和定义在<SPAN>mmsystem.h</SPAN>头文件和<SPAN>Winmm.lib</SPAN>库中。所以如果程序中用到这些函数,必须包含<SPAN>mmsystem.h</SPAN>这个头文件,同时导进<SPAN>Winmm.lib</SPAN>库。如下:</P>
<DIV
style="BORDER-RIGHT: navy 1pt solid; PADDING-RIGHT: 4pt; BORDER-TOP: navy 1pt solid; PADDING-LEFT: 4pt; BACKGROUND: #f3f3f3; PADDING-BOTTOM: 1pt; BORDER-LEFT: navy 1pt solid; PADDING-TOP: 1pt; BORDER-BOTTOM: navy 1pt solid">
<P class=SourceCode><SPAN>#include "mmsystem.h"</SPAN></P>
<P class=SourceCode><SPAN>#pragma
comment(lib,"Winmm.lib")</SPAN></P></DIV>
<P> 双<SPAN>/</SPAN>多缓冲技术可以很好的实现声音的快速连续采集和实时顺畅播放。采集声音时,缓冲满了会有一个消息,程序在响应这个消息需要几毫秒~几十毫秒甚至更多的时间,假设为<SPAN>Xms</SPAN>,如果只使用一个缓冲,程序必须在响应完该消息才再次采集声音,那么在这<SPAN>Xms</SPAN>的时间里,没有采集到任何声音;声音的播放也是一样的道理,这样声音就会不连续。因此双缓冲或多缓冲技术是必要的,让输入和输出设备可以循环使用这些缓冲,当程序在响应某块缓冲数据已满或播放完毕消息时,声卡可以继续往下一块缓冲添加数据或播放下一块缓冲的数据,如此循环保障声音的连续性。</P>
<H1><A id=_Toc162973507 name=_Toc162973507><SPAN>3 </SPAN>相关数据结构</A></H1>
<P> 声音在采集(录音)和播放的时需要有一些统一的格式,包括音频格式类型,声道,采样率等信息。下面的数据结构具体描述了该格式:</P>
<DIV
style="BORDER-RIGHT: navy 1pt solid; PADDING-RIGHT: 4pt; BORDER-TOP: navy 1pt solid; PADDING-LEFT: 4pt; BACKGROUND: #f3f3f3; PADDING-BOTTOM: 1pt; BORDER-LEFT: navy 1pt solid; PADDING-TOP: 1pt; BORDER-BOTTOM: navy 1pt solid">
<P class=SourceCode><SPAN>typedef struct tWAVEFORMATEX</SPAN></P>
<P class=SourceCode><SPAN>{</SPAN></P>
<P class=SourceCode><SPAN>
WORD wFormatTag;</SPAN></P>
<P class=SourceCode><SPAN>
WORD nChannels;</SPAN></P>
<P class=SourceCode><SPAN>
DWORD nSamplesPerSec;</SPAN></P>
<P class=SourceCode><SPAN>
DWORD nAvgBytesPerSec;</SPAN></P>
<P class=SourceCode><SPAN>
WORD nBlockAlign;</SPAN></P>
<P class=SourceCode><SPAN>
WORD wBitsPerSample;</SPAN></P>
<P class=SourceCode><SPAN>
WORD cbSize;</SPAN></P>
<P class=SourceCode><SPAN>} WAVEFORMATEX, *PWAVEFORMATEX, </SPAN></P>
<P class=SourceCode><SPAN> NEAR *NPWAVEFORMATEX, FAR
*LPWAVEFORMATEX;</SPAN></P></DIV>
<P> 其中,<SPAN>wFormatTag</SPAN>是音频格式类型,<SPAN>nChannels</SPAN>是声道数,<SPAN>nSamplesPerSec</SPAN>是采样频率,<SPAN>nAvgBytesPerSec</SPAN>是每秒钟的字节数,<SPAN>nBlockAlign</SPAN>是每个样本的字节数,<SPAN>wBitsPerSample</SPAN>是每个样本的量化位数,<SPAN>cbSize</SPAN>是附加信息的字节大小。</P>
<P> 在打开声卡输入和输出设备之前,必须对音频的相关参数进行设置。在后面章节中将给出<SPAN>WAVE_FORMAT_PCM</SPAN>格式音频的详细参数设置。</P>
<P> 音频数据块有一个头结构,这个结构包含了音频数据缓冲的地址,大小,已录音数据大小等信息和其他各种控制标志。这个结构适用于音频的输入(录音)和输出(播放)缓冲中。下面是该结构的详细信息:</P>
<DIV
style="BORDER-RIGHT: navy 1pt solid; PADDING-RIGHT: 4pt; BORDER-TOP: navy 1pt solid; PADDING-LEFT: 4pt; BACKGROUND: #f3f3f3; PADDING-BOTTOM: 1pt; BORDER-LEFT: navy 1pt solid; PADDING-TOP: 1pt; BORDER-BOTTOM: navy 1pt solid">
<P class=SourceCode><SPAN>typedef struct wavehdr_tag </SPAN></P>
<P class=SourceCode><SPAN>{</SPAN></P>
<P class=SourceCode><SPAN>
LPSTR lpData;</SPAN></P>
<P class=SourceCode><SPAN>
DWORD dwBufferLength;</SPAN></P>
<P class=SourceCode><SPAN>
DWORD dwBytesRecorded;</SPAN></P>
<P class=SourceCode><SPAN>
DWORD dwUser;</SPAN></P>
<P class=SourceCode><SPAN>
DWORD dwFlags;</SPAN></P>
<P class=SourceCode><SPAN>
DWORD dwLoops;</SPAN></P>
<P class=SourceCode><SPAN>
struct wavehdr_tag FAR *lpNext;</SPAN></P>
<P class=SourceCode><SPAN>
DWORD reserved;</SPAN></P>
<P class=SourceCode><SPAN>} WAVEHDR, *PWAVEHDR, NEAR *NPWAVEHDR, FAR
*LPWAVEHDR;</SPAN></P></DIV>
<P> 其中,<SPAN>lpData</SPAN>是指定的缓冲块地址,<SPAN>dwBufferLength</SPAN>是指定的缓冲块大小,<SPAN>dwBytesRecorded</SPAN>是已录音数据大小,<SPAN>dwUser</SPAN>是用户数据,<SPAN>dwFlags</SPAN>是控制标志,表明缓冲的使用状态,<SPAN>dwLoops</SPAN>是音频输出时缓冲数据块循环的次数,<SPAN>lpNext</SPAN>和<SPAN>reserved</SPAN>是系统保留数据。在程序实现时,通过设置或修改这个结构的相关参数来实现对音频输入和输出缓冲区的控制。</P>
<P> 程序中定义了一个队列结构,用来存储网络中接收到的音频数据,其结构如下:</P>
<DIV
style="BORDER-RIGHT: navy 1pt solid; PADDING-RIGHT: 4pt; BORDER-TOP: navy 1pt solid; PADDING-LEFT: 4pt; BACKGROUND: #f3f3f3; PADDING-BOTTOM: 1pt; BORDER-LEFT: navy 1pt solid; PADDING-TOP: 1pt; BORDER-BOTTOM: navy 1pt solid">
<P class=SourceCode><SPAN>struct CAudioOutData</SPAN></P>
<P class=SourceCode><SPAN>{</SPAN></P>
<P class=SourceCode><SPAN> short *lpdata;</SPAN></P>
<P class=SourceCode><SPAN> DWORD dwLength;</SPAN></P>
<P class=SourceCode><SPAN>};</SPAN></P>
<P class=SourceCode><SPAN>struct CAudioOutData
m_AudioDataOut[50];</SPAN></P></DIV>
<P> 其中,<SPAN>lpdata</SPAN>是数据块地址,<SPAN>dwLength</SPAN>是数据块大小。通过调整<SPAN>m_AudioDataOut</SPAN>下标实现队列的循环过程。</P>
<H1><A id=_Toc162973508 name=_Toc162973508><SPAN>4 </SPAN>参数设置</A></H1>
<P> 声卡输入和输出的音频属性可定义如下:</P>
<DIV
style="BORDER-RIGHT: navy 1pt solid; PADDING-RIGHT: 4pt; BORDER-TOP: navy 1pt solid; PADDING-LEFT: 4pt; BACKGROUND: #f3f3f3; PADDING-BOTTOM: 1pt; BORDER-LEFT: navy 1pt solid; PADDING-TOP: 1pt; BORDER-BOTTOM: navy 1pt solid">
<P
class=SourceCode><SPAN>m_waveformt.wFormatTag
= WAVE_FORMAT_PCM;</SPAN></P>
<P
class=SourceCode><SPAN>m_waveformt.nChannels
= 1;</SPAN></P>
<P class=SourceCode><SPAN>m_waveformt.nSamplesPerSec
= 8000;</SPAN></P>
<P class=SourceCode><SPAN>m_waveformt.wBitsPerSample =
16;</SPAN></P>
<P
class=SourceCode><SPAN>m_waveformt.cbSize
= 0;</SPAN></P>
<P
class=SourceCode><SPAN>m_waveformt.nBlockAlign
= 2;</SPAN></P>
<P class=SourceCode><SPAN>m_waveformt.nAvgBytesPerSec =
16000;</SPAN></P></DIV>
<P> 设置的音频格式类型是<SPAN>PCM</SPAN>格式,单通道,<SPAN>8000HZ</SPAN>的采样率,每秒采集的数据大小为<SPAN>16000bytes.</SPAN>其中,存在着下面的关系:</P>
<DIV
style="BORDER-RIGHT: navy 1pt solid; PADDING-RIGHT: 4pt; BORDER-TOP: navy 1pt solid; PADDING-LEFT: 4pt; BACKGROUND: #f3f3f3; PADDING-BOTTOM: 1pt; BORDER-LEFT: navy 1pt solid; PADDING-TOP: 1pt; BORDER-BOTTOM: navy 1pt solid">
<P class=SourceCode><SPAN>nBlockAlign
= nChannels * wBitsPerSample / 8
; </SPAN></P>
<P class=SourceCode><SPAN>nAvgBytesPerSec =
nSamplesPerSec * nBlockAlign ;</SPAN></P></DIV>
<P> 音频数据块头结构可定义如下:</P>
<DIV
style="BORDER-RIGHT: navy 1pt solid; PADDING-RIGHT: 4pt; BORDER-TOP: navy 1pt solid; PADDING-LEFT: 4pt; BACKGROUND: #f3f3f3; PADDING-BOTTOM: 1pt; BORDER-LEFT: navy 1pt solid; PADDING-TOP: 1pt; BORDER-BOTTOM: navy 1pt solid">
<P
class=SourceCode><SPAN>pWaveHdr->lpData
= </SPAN>指定缓冲的地址<SPAN>;</SPAN></P>
<P class=SourceCode><SPAN>pWaveHdr->dwBufferLength =
</SPAN>指定缓冲的大小<SPAN>;</SPAN></P>
<P class=SourceCode><SPAN>pWaveHdr->dwBytesRecorded = 0
;</SPAN></P>
<P
class=SourceCode><SPAN>pWaveHdr->dwUser
= 0 ;</SPAN></P>
<P
class=SourceCode><SPAN>pWaveHdr->dwFlags
= 0 ;</SPAN></P>
<P
class=SourceCode><SPAN>pWaveHdr->dwLoops
= 1 ;</SPAN></P>
<P
class=SourceCode><SPAN>pWaveHdr->lpNext
= NULL;</SPAN></P>
<P
class=SourceCode><SPAN>pWaveHdr->reserved
= 0;</SPAN></P>
<P class=SourceCode><SPAN>m_waveformt.nAvgBytesPerSec =
16000;</SPAN></P></DIV>
<P> 每次为输入或输出设备准备缓存的时候,都需要设置缓存数据块的头结构。</P>
<H1><A id=_Toc162973509 name=_Toc162973509><SPAN>5 </SPAN>基本操作流程</A></H1>
<P> 调用<SPAN>WaveX </SPAN>低级音频函数<SPAN>API</SPAN>启动声卡录音的基本操作步骤如下图所示:</P>
<DIV
style="BORDER-RIGHT: windowtext 1pt solid; PADDING-RIGHT: 4pt; BORDER-TOP: windowtext 1pt solid; PADDING-LEFT: 4pt; BACKGROUND: #ccffcc; PADDING-BOTTOM: 1pt; BORDER-LEFT: windowtext 1pt solid; PADDING-TOP: 1pt; BORDER-BOTTOM: windowtext 1pt solid">
<P class=TextGraph
style="PADDING-RIGHT: 0pt; PADDING-LEFT: 0pt; BACKGROUND: #ccffcc; PADDING-BOTTOM: 0pt; BORDER-TOP-STYLE: none; PADDING-TOP: 0pt; BORDER-RIGHT-STYLE: none; BORDER-LEFT-STYLE: none; BORDER-BOTTOM-STYLE: none"><SPAN>
</SPAN>打开录音设备:<SPAN>
waveInOpen</SPAN></P>
<P class=TextGraph
style="PADDING-RIGHT: 0pt; PADDING-LEFT: 0pt; BACKGROUND: #ccffcc; PADDING-BOTTOM: 0pt; BORDER-TOP-STYLE: none; PADDING-TOP: 0pt; BORDER-RIGHT-STYLE: none; BORDER-LEFT-STYLE: none; BORDER-BOTTOM-STYLE: none"><SPAN>
</SPAN>↓</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -