日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Win32下的录音编程

發(fā)布時間:2025/3/15 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Win32下的录音编程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
1 引言
  在Win32 APIs基礎上編寫錄音程序繁瑣易錯,使用封裝好的類是個不錯的注意。不幸的是所謂封裝好的類對你而言,往往是代碼羅嗦且功能不足,因此盡管你可能希望在某個項目上因使用封裝好的類而避開Win32 APIs,可最終你發(fā)現(xiàn)你還得面對它。不是為了編寫自己的類,就是為了修改別人的代碼。
  Win32 APIs中有一組被稱成多媒體控制接口(即MCI)的函數(shù),該接口提供了多媒體編程所需的系統(tǒng)級APIs。對絕大多數(shù)C/C++程序員而言,這些函數(shù)也就是Windows多媒體編程的最低層接口。
  由于錄音代碼直接操作真實的錄音設備而非單純的邏輯過程,因此會遇到一些“意外”或與時序有關的困難,從而使編寫健壯的代碼成了一件困難的事。
  錄音的目的往往是將聲音寫入文件保存下來或是通過網絡發(fā)送,這兩類需求對錄音代碼影響較大。前者無實時性要求,一般也不限制數(shù)據(jù)量,代碼較易編寫,而后者既對實時性又要求,又對數(shù)據(jù)量(也就是音頻格式)有要求,且網絡系統(tǒng)的穩(wěn)定性也難比文件系統(tǒng)。期望在本文中試圖解決所有的問題是不現(xiàn)實的,諸如如何壓縮音頻數(shù)據(jù)以減小網絡帶寬需求或如何通過網絡傳輸音頻數(shù)據(jù)之類的問題,讀者需參閱專業(yè)著作,本文只講述如何獲得適宜某類需求的原始音頻數(shù)據(jù)。
  本文中的示例代碼均是一個名為CWaveRecord的錄音類的成員函數(shù),該類是一個網絡電話程序的核心類。為減小篇幅或簡單明了,作了簡化處理等。
  2 基本過程
  MCI按打開設備、配置設備、實現(xiàn)功能(或曰發(fā)送命令)、撤銷配置、關閉設備的標準次序組織APIs。對于錄音編程而言,其要點在于根據(jù)音頻格式打開對應的設備、配置錄音所需的參數(shù)(主要是設置數(shù)據(jù)區(qū)以及根據(jù)數(shù)據(jù)接收方式設置回調函數(shù)或消息)、按一定次序發(fā)送命令給設備、接收數(shù)據(jù)并配置參數(shù)以繼續(xù)錄音、停止錄音釋放資源、關閉設備等幾個步驟上。所需的函數(shù)說明于mmsystem.h,引入庫是winmm.lib。
  2.1 打開設備
  調用waveInOpen()打開錄音設備,打開成功后函數(shù)返回MMSYSERR_NOERROR,而第一個參數(shù)返回設備句柄。
  調用該函數(shù)時,必須指定設備表示符(可能有多個設備)、音頻格式以及返回音頻數(shù)據(jù)的方式。
  你可以將設備表示符,即第二個參數(shù)設為WAVE_MAPPER。這意味著,你不在意具體使用哪個設備,由系統(tǒng)決定。
  第三個參數(shù)是個WAVEFORMATEX結構指針,對應的結構指定音頻格式。一個音頻格式結構的例子:
  PCMWAVEFORMAT wf_PCM8S11K =
  {
   {
   WAVE_FORMAT_PCM, // PCM格式
   1, // 單聲道
   11025L, // 11.025KHZ
   11025L, // 11025字節(jié)/s
   1 // 字節(jié)對齊
   },
   8 // 8位采樣
  };
  后面的三個參數(shù)用于指定獲取音頻數(shù)據(jù)的方式:事件方式、線程方式、窗口方式以及回調函數(shù)方式。最后一個參數(shù),即第六個參數(shù)指定方式,而第四個參數(shù)和第五個參數(shù)指定該方式所需的信息。
  調用示例:
  waveInOpen( &m_hWaveIn, dwDeviceID, m_pwf,
  (DWORD)waveInProc, (DWORD)this, CALLBACK_FUNCTION );
  該示例使用回調函數(shù)方式獲取音頻數(shù)據(jù)。其中dwDeviceID通常即為WAVE_MAPPER,m_pwf指向音頻格式結構,缺省值即wf_PCM8S11K的地址,waveInProc是回調函數(shù)(內部細節(jié)后面論述)。值得注意的是,this指針被設為實例數(shù)據(jù),這是一個編程小技巧,方便waveInProc調用處理函數(shù)。
  2.2 配置設備
  按指定音頻格式成功打開錄音設備之后,需要配置錄音設備,也即為音頻數(shù)據(jù)分配數(shù)據(jù)區(qū)。需要考慮的第一個問題是數(shù)據(jù)塊的個數(shù)和大小。
  系統(tǒng)一般在填滿一個數(shù)據(jù)塊后將其返回,這意味著大的數(shù)據(jù)塊導致獲取數(shù)據(jù)的頻率降低。如果試圖通過網絡傳輸這些數(shù)據(jù),這意味著傳輸次數(shù)降低,但每次傳輸?shù)臄?shù)據(jù)量較大,這將導致音頻的實時性降低。另外,在網絡傳輸過程中,數(shù)據(jù)包(指每次發(fā)送的UDP或TCP數(shù)據(jù)包)的開始一般需設置一個信息頭,紀錄本數(shù)據(jù)包所含音頻數(shù)據(jù)的長度、錄制起始時間、錄制時間、發(fā)送時間、識別用的標識符以及其他一些信息,方便接受者處理。如果設置的音頻數(shù)據(jù)塊太小,比如100字節(jié),將導致低的網絡傳輸效率。這意味著網絡將為小量數(shù)據(jù)啟動傳輸過程,而這小量數(shù)據(jù)中又有相當一部分屬于附加信息,這是不合算的。
  數(shù)據(jù)塊的個數(shù)相對來說要好處理的多。較多的數(shù)據(jù)塊只有一個問題,那就是浪費,但這對Windows這樣的操作系統(tǒng)而言,浪費幾百K的內存根本不是問題。數(shù)據(jù)塊個數(shù)不能太少,這是因為在你處理上次返回的數(shù)據(jù)塊時,錄音設備正在工作,需要內存。如果在某一段時間內,比如說網絡傳輸變壞,你處理數(shù)據(jù)的速度也將變慢,而錄音設備卻如常工作。這導致你無法迅速地返回當前內存供下一次錄音使用,因此需要一些備用的內存塊以備不時之需。
  調用waveInAddBuffer為錄音設備配置數(shù)據(jù)塊,而在此之前需調用waveInPrepareHeader準備數(shù)據(jù)塊。
  WaveInPrepareHeader的參數(shù)有三個,就是錄音設備句柄、一個描述數(shù)據(jù)塊的結構WAVEHDR和該結構的大小。
  WAVEHDR結構是錄音編程的常用數(shù)據(jù)結構,不同的使用環(huán)境設置不同的參數(shù),具體內容參見MSDN。
  下面的示例為錄音設備添加4個大小為4096字節(jié)的數(shù)據(jù)塊:
   for( i=0; i<4; i++ )
   {
   pwh = (WAVEHDR*)malloc( 4096 + sizeof(WAVEHDR) )
   ZeroMemory( pwh, sizeof(WAVEHDR) );
   pwh->dwBufferLength = 4096;
   pwh->lpData = (LPSTR)(pwh + 1);
   waveInPrepareHeader( hWaveIn, pwh, sizeof(WAVEHDR) );
   waveInAddBuffer( hWaveIn, pwh, sizeof(WAVEHDR) );
  }
  每次添加數(shù)據(jù)塊都需一個WAVEHDR(其他操作也如此),能否讓這些數(shù)據(jù)塊共用一個WAVEHDR或使用局部變量以減少內存消耗呢?最好不要這么做,因為你是在與實際設備打交道。很可能當調用返回時,系統(tǒng)內部卻還在使用WAVEHDR結構。如果共用或使用局部變量將導致信息混亂或非法內存操作。這一點MSDN沒有強調,我花了一星期才得到如此教訓。其他錄音操作也類似于此,切記!
  配置好內存以后就可啟動錄音了:
  waveInStart( hWaveIn );
  如果所有操作均正確,很快你將得到第一塊音頻數(shù)據(jù)。
  2.3 處理音頻數(shù)據(jù)塊
  獲取音頻數(shù)據(jù)塊的方式有多種(詳見MSDN),處理方式大同小異,本文以回調函數(shù)方式為例說明處理過程。如上所述,waveInProc在waveInOpen調用中被設置為回調函數(shù)。
  waveInProc通過處理WIM_OPEN(打開設備)、WIM_DATA(音頻數(shù)據(jù))、WIM_CLOSE(設備關閉)消息的方式與系統(tǒng)交互,通常只需處理WIM_DATA消息,其他兩條消息可以忽略。
  當系統(tǒng)返回一個音頻數(shù)據(jù)塊時,系統(tǒng)調用回調函數(shù)。此時,回調函數(shù)第一個參數(shù)被設置為錄音設備句柄;第二個參數(shù)是消息,即WIM_DATA;第三個參數(shù)則是在waveInOpen第五個參數(shù)中給出的實例數(shù)據(jù);其他參數(shù)是與消息有關的參數(shù),參見MSDN。
   處理WIM_DATA的任務由成員函數(shù)OnWaveInData完成,因此waveInProc及其簡單:
   CWaveRecord *pObject = (CWaveRecord *)dwInstance;
   if( uMsg == WIM_DATA )
   pObject->OnWaveInData( (WAVEHDR*)dwParam1 );
   在OnWaveInData中需要完成兩件事:一是保存系統(tǒng)返回的音頻數(shù)據(jù),二是給錄音設備添加數(shù)據(jù)塊以繼續(xù)錄音,否則會因數(shù)據(jù)塊耗盡而停止錄音。
  在保存系統(tǒng)返回的音頻數(shù)據(jù)塊之前,需要調用waveInUnprepareHeader發(fā)送一個通知告訴設備驅動程序該數(shù)據(jù)塊已不能用于錄音。保存音頻數(shù)據(jù)塊是個單純的數(shù)據(jù)保存問題,不需多說,只是有三個要點需要特別關注:
  一是MSDN中注明在回調函數(shù)中嚴格限制系統(tǒng)調用,因此你不能隨心所欲地設計方案,比如說直接將音頻數(shù)據(jù)寫入文件是不允許的。通常,你需要設計一個數(shù)據(jù)塊鏈表結構進行緩沖,這多半涉及多線程編程。特別是如果你使用MFC中的CSocket類,那么必須清楚CSocket依賴一個名為CSocketWnd的內部類,而該類與TLS(線程局部存貯)有關,這意味著CSocket類是不支持多線程共享的。然而,Win32的SOCKET句柄是全局性。因此,獲取CSocketWnd類對象的窗口句柄之后,我通過直接調用Win32 APIs使用CSocket::m_hSocket避開這個由MFC封裝引起的問題。
  二是系統(tǒng)是在另一個線程(不是你自己創(chuàng)建的線程)中調用回調函數(shù),這會給使用TLS機制的程序帶來一些微妙的影響。如果你使用MFC,AfxGetApp之類的函數(shù)返回值不正確。
  三是盡可能地快速。
  至于給錄音設備添加數(shù)據(jù)塊以繼續(xù)錄音,其步驟與初始化過程一樣:
  waveInPrepareHeader( hWaveIn, pwh, sizeof(WAVEHDR));
  waveInAddBuffer( hWaveIn, pwh, sizeof(WAVEHDR) );
  2.4 關閉設備釋放資源
  簡單的調用waveInClose即可關閉設備。不過,在關閉時必須保證所有錄音數(shù)據(jù)塊已全部返回,這有點麻煩。我的解決方法是設置一個停止錄音標志和一個錄音數(shù)據(jù)塊計數(shù)。當用戶發(fā)出停止錄音的命令后,該標志置為TRUE,而OnWaveInData(在回調函數(shù)中被調用)檢查該標志,在標志置位的情況下,OnWaveInData不添加錄音數(shù)據(jù)塊。每添加一個錄音數(shù)據(jù)塊計數(shù)增加,每返回一個則減少(均在OnWaveInData中實現(xiàn))。執(zhí)行停止錄音命令時,先將停止標志值位,等待計數(shù)變?yōu)榱?#xff0c;然后才調用waveInClose。需要注意的是,計數(shù)涉及多線程數(shù)據(jù)共享,應使用線程互斥機制,在單CPU的機子上使用InterlockedIncrement和InterlockedDecrement是個簡單的選擇。
  3 編程技巧
  3.1 簡單的聲音檢測
  如果你通過網絡發(fā)送音頻數(shù)據(jù),自然需要進行音頻處理。專業(yè)級的處理難度相當高,如果你是在一個局域網上進行通話,那么無需進行音頻壓縮也可以將就使用(性能自然不高)。但如果你不說話,程序還不停地發(fā)送音頻數(shù)據(jù)(靜音或噪音),那就太說不過去了。
  你可以定義一個靜音區(qū)間,一旦音頻強度進入該區(qū)間就意味著沒有聲音。統(tǒng)計音頻數(shù)據(jù)塊中不是靜音采樣點的個數(shù),當總數(shù)超過預設的限制時,才發(fā)送該數(shù)據(jù)塊。
  對于8位采樣,127是靜音點。簡單的代碼如下:
   for( i=0; i<nMax; i++ )
   {
   uValue = (UCHAR)data[i];
   if( uValue<=125 || uValue>=129 )
   uHasCnt++;
   if( uHasCnt >= uLestNum )
   return TRUE;
   }
  return FALSE;
  3.2 創(chuàng)建音頻文件
  創(chuàng)建任意格式的音頻文件在編程上不是一件輕松的事,但對于8位單聲道音頻數(shù)據(jù)而言較容易。定義文件信息頭結構:
  // wav文件頭結構, 對齊方式為1字節(jié)
  typedef struct
  {
  char RiffId[4]; // "RIFF"
   DWORD dwFileDataSize; // file size - 8
   char WaveId[4]; // "WAVE"
   char FMTId[4]; // "fmt "
   DWORD dwFmtSize; // 16
   WORD wFormatTag; // WAVE_FORMAT_PCM
   WORD wChannels; // 1
   DWORD dwSamplesPerSec; // 11025
   DWORD dwAvgBytesPerSec; // 11025
   WORD wBlockAlign; // 1
   WORD wBitsPerSample; // 8
   char DataId[4]; // "data"
   DWORD dwDataSize;
  } WAVEFILEHDR, *PWAVEFILEHDR;
  打開文件時,預留該結構大小的空間:
  SetFilePointer(hFile,sizeof(WAVEFILEHDR), NULL, FILE_BEGIN );
  然后就如寫一般文件一樣將接收的音頻數(shù)據(jù)寫入文件:
   WriteFile(hFile,pwh->lpData,pwh->dwBytesRecorded,&n, NULL );
   m_dwSizeWrite += dwSize;
  最后一行代碼是將寫入的數(shù)據(jù)量統(tǒng)計下來用于信息頭填寫。
  在關閉音頻文件時填寫信息頭:
   WAVEFILEHDR wfd =
   {
   'R', 'I', 'F', 'F', 0,
   'W', 'A', 'V', 'E',
   'f', 'm', 't', ' ', 16,
   WAVE_FORMAT_PCM, 1, 11025, 11025, 1, 8,
   'd', 'a', 't', 'a', 0
   };
   wfd.dwFileDataSize = dwBytes + sizeof(WAVEFILEHDR)- 8;
   wfd.dwDataSize = dwBytes;
   ::SetFilePointer( hFile, 0, NULL, FILE_BEGIN );
   ::WriteFile( hFile, &wfd, sizeof(wfd), &dwSize, NULL );
  參考文獻
  1 Visual C++ 6.0 MSDN
  2 Win32程序員開發(fā)指南(二)
  3 Visual C++ 6.0附帶MFC源碼

總結

以上是生活随笔為你收集整理的Win32下的录音编程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。