API 层实现语音播放

类别:VC语言 点击:0 评论:0 推荐:


    昨天写了语音录制(见 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  *****************************

本文地址:http://com.8s8s.com/it/it1888.htm