昨天写了语音录制(见 http://www.csdn.net/develop/Read_Article.asp?Id=17627 ),现在继续讲语音播放。 要用到 .wav 文件头内容部分的请参看上一文《语音录制》 里的相关介绍。(我希望把这两个模块用在我正做的local语音通讯试验中)
好的,上次的程序生成了一个 "myTest.wav" 的音频文件,根据上次的文件格式,那么从开头数第21个字节开始就是 WAVEFORMATEX 的结构了,提供你的指针、读到你的结构中去吧。 还有,裸音频数据的长度,在开头第43个字节开始的地方,然后,从开头数第47个字节的位置就是音频裸数据的起始地址了,把数据也读入到你的缓冲中吧。(如果地址从00开始算,刚才给出的位置就减1)
小事已经具备,开始干吧。按照上次的编排,先讲一下用到的 API 。放音通常都是使用 waveOutXXX一类的 API 。 最主要的是 waveOutWrite (马上播放指定的音频缓冲),配对的是 waveOutReset(马上停止声音的播放), 外加对播放的控制 waveOutPause (暂停播放,注意: 可以在 waveOutWrite 前就已经暂停 ) 和 waveOutRestart (继续被暂停的播放)。
说完这些函数,也提一下为以上几个函数做准备工作的函数吧, waveOutOpen 和 waveOutClose 配对( waveOutOpen 里面指定音频格式,还有通知回调函数的地址 ),waveOutPrepareHeader 和 waveOutUnprepareHeader 配对(也是在 waveOutPrepareHeader 里面指定用来播放的缓冲大小和首地址)就是这么多了。
下面看详细调用过程简介。
waveOutOpen (指定音频格式和回调函数地址)
waveOutPrepareHeader (指定音频缓冲的地址)
waveOutWrite
(放音中....)
waveOutPause (你可以尝试在 waveOutWrite 之前暂停)
(声音暂停)
waveOutRestart
(继续播放中...)
waveOutReset
waveOutUnprepareHeader
waveOutClose
小提示:最好在 waveOutReset 前先执行 waveOutPause 否则在我的机器上会有一个噪音发生???
需要指出的是,在播放时并不能动态的知道这个声音什么时候就播放完了(总不能大概估算时间就执行 waveOutReset 吧),为了处理这个问题,我想了一个解决办法,就是打开设备的时候指定通知回调函数,在那个回调函数中执行 waveOutReset 就行了(实现时作了一点小改动)
下面将给出源程序,执行时候最好用上次介绍录音的模块生成 "myTest.wav" 文件,然后拷贝到当前目录下用本程序执行播放,(该源程序中要包含调试类文件 RunTimeLog.cpp,见http://www.csdn.net/develop/Read_Article.asp?Id=17477 详细调试信息请看 "XXX.log") (另:程序只是做试验用,没有注释,敬请见谅 )
(全文完 - 2003年03月28日_pm: 03时35分 `海风: 昨天忘署名了)
// ******************* FileName: WinMain.cpp *****************************
// 该源程序需要加入到 VC6 的 Win32 Application 的 empty Project 中
// 请包含我自定义的调试类,见 #include "RunTimeLog.cpp"
// 对于工程的 Link 选项,至少要包含以下库: msvcrt.lib kernel32.lib user32.lib Winmm.lib
#define WIN32_LEAN_AND_MEAN // Say No to MFC
#include <windows.h>
#include <Mmsystem.h>
#include "RunTimeLog.cpp"
RunTimeLog log;
char lpTemp[256]="";
DWORD CurThreadID;
DWORD FCC(LPSTR lpStr)
{
DWORD Number = lpStr[0] + lpStr[1] *0x100 + lpStr[2] *0x10000 + lpStr[3] *0x1000000 ;
return Number;
}
void CALLBACK
waveOutProc( HWAVEOUT hwo,
UINT uMsg,
DWORD dwInstance,
DWORD dwParam1,
DWORD dwParam2 )
{
;
log.numberwrite( "Get a waveOutProc uMsg =", uMsg );
if (uMsg == WOM_DONE)
{
log.write("WOM_DONE");
PostThreadMessage( CurThreadID, WM_QUIT , 11, 22 ); // WM_QUIT
}
return ;
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow )
{
CreateMutex( NULL, false, "MyMutex");
if ( GetLastError() == ERROR_ALREADY_EXISTS ) { log.write("Exists and Exit"); log.last(); ExitProcess( NULL); }
CurThreadID = GetCurrentThreadId( ); // 取得当前线程的 ID (标识号)
log.write("Program Start."); // log.msg("Start Test");
log.nobuff = true;
DWORD datasize = 48000;
WAVEFORMATEX waveformat;
waveformat.wFormatTag=0; //WAVE_FORMAT_PCM;
waveformat.nChannels=0; //1;
waveformat.nSamplesPerSec=0; //8000;
waveformat.nAvgBytesPerSec=0; //8000;
waveformat.nBlockAlign=0; //1;
waveformat.wBitsPerSample=0; //8; //指定录音格式
waveformat.cbSize=0;
wsprintf( lpTemp, "WAVEFORMATEX size = %lu", sizeof(WAVEFORMATEX) );
log.write(lpTemp);
// 打开文件,然后读入全部的文件长度,
HANDLE fileHandle =
CreateFile( "myTest.wav", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (fileHandle) log.write("打开可读文件"); else log.write("尝试打开文件失败");
if (fileHandle)
{
DWORD dwtemp = 0; // FCC("RIFF");
DWORD ReadCount = 0;
DWORD bufflong = 0;
ReadFile( fileHandle , &dwtemp, 4, &ReadCount, NULL );
if ( dwtemp == FCC("RIFF") ) log.write("找到 RIFF 文件标志"); else log.write("没有找到 RIFF 文件标志");
ReadFile( fileHandle , &dwtemp, 4, &ReadCount, NULL );
bufflong = dwtemp;
log.numberwrite("总的文件大小", bufflong + 8 );
log.numberwrite("真的文件大小", GetFileSize(fileHandle, NULL) );
if ( (bufflong+8)!=GetFileSize(fileHandle, NULL) ) bufflong = GetFileSize(fileHandle, NULL) - 8;
SetFilePointer( fileHandle, 8, NULL, FILE_CURRENT);
bufflong = bufflong - 8;
ReadFile( fileHandle , &dwtemp, 4, &ReadCount, NULL );
bufflong = bufflong - 4;
log.numberwrite( "sizeof(WAVEFORMAT) =", dwtemp );
ReadFile( fileHandle , &waveformat, dwtemp, &ReadCount, NULL );
bufflong = bufflong - dwtemp;
log.numberwrite( "waveformat.wBitsPerSample =", waveformat.wBitsPerSample );
log.numberwrite( "waveformat.nSamplesPerSec =", waveformat.nSamplesPerSec );
while (dwtemp != bufflong)
{
ReadFile( fileHandle , &dwtemp, 4, &ReadCount, NULL );
bufflong = bufflong - 4;
if ( (bufflong < 8)||(bufflong < bufflong - 128) ) { log.write("找不到准确的缓冲大小"); break; }
}
log.numberwrite( "bufflong =", bufflong );
datasize = bufflong;
HWAVEOUT phwo;
if ( waveOutGetNumDevs() ) log.write("有可以使用的 WaveOut 通道"); else log.write("没有可以使用的 waveOut 通道");
int res=waveOutOpen( &phwo, WAVE_MAPPER, &waveformat, (DWORD)waveOutProc, NULL, CALLBACK_FUNCTION ); //打开录音设备
if ( res == MMSYSERR_NOERROR ) log.write("打开 waveOut 成功"); // 验证创建是否成功
else log.numberwrite( "打开 waveOut 失败,Error_Code =", res );
WAVEHDR m_pWaveHdr;
m_pWaveHdr.lpData = (char *)GlobalLock( GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE, datasize) );
memset(m_pWaveHdr.lpData, 0, datasize );
// 读入磁盘数据
ReadFile( fileHandle , m_pWaveHdr.lpData, datasize, &ReadCount, NULL );
m_pWaveHdr.dwBufferLength = datasize;
m_pWaveHdr.dwBytesRecorded = 0;
m_pWaveHdr.dwUser = 0;
m_pWaveHdr.dwFlags = 0;
m_pWaveHdr.dwLoops = 0;
log.numberwrite( "WAVEHDR size =", sizeof(WAVEHDR) );
UINT resPrepare = 0;
resPrepare = waveOutPrepareHeader( phwo, &m_pWaveHdr, sizeof(WAVEHDR) );
if ( resPrepare == MMSYSERR_NOERROR) log.write("准备放音用头文件成功");
else log.numberwrite( "不能开辟放音头文件,Error_Code =", resPrepare );
if ( waveOutPause(phwo) ) log.write("无法 Pause"); else log.write("成功 Pause");
if ( waveOutWrite( phwo, &m_pWaveHdr, sizeof(WAVEHDR) ) ) log.write("开始写入放音数据失败");
else log.write("开始写入放音数据成功");
if ( waveOutRestart(phwo) ) log.write(" 非法 Resume"); else log.write(" Resume 到 Playback");
log.write(""); // 写入空字符串可以分行
PostThreadMessage( CurThreadID, WM_USER , 11, 22 ); // WM_QUIT
// 进入消息循环
MSG msg;
while(1)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{ log.numberwrite( "Get The uMsg =", msg.message ); if (msg.message == WM_QUIT) break; }
else { log.write("没有取到消息"); WaitMessage(); }
} // end while
if ( waveOutPause(phwo) ) log.write("无法 Pause"); else log.write("成功 Pause");
if ( waveOutReset(phwo) ) log.write("不能 Reset"); else log.write("成功 Reset");
resPrepare = waveOutUnprepareHeader( phwo, &m_pWaveHdr, sizeof(WAVEHDR) );
if ( resPrepare == MMSYSERR_NOERROR) log.write("释放放音用头文件成功");
else log.numberwrite( "不能释放放音头文件,Error_Code =", resPrepare );
if ( m_pWaveHdr.lpData )
if ( GlobalFree(GlobalHandle( m_pWaveHdr.lpData )) ) log.write("Global Free 失败"); else log.write("Global Free 成功");
if (res == MMSYSERR_NOERROR ) //关闭录音设备
if (waveOutClose(phwo)==MMSYSERR_NOERROR)log.write("正常关闭放音设备");
else log.write("非正常关闭放音设备");
CloseHandle(fileHandle); fileHandle = INVALID_HANDLE_VALUE;
}
log.last(true);
// ExitProcess(0);
return 0;
}
// ******************* End of File *****************************