使用 攝像頭、麥克風、揚聲器測試程序 一文中提到的技術,我們可以基本實現QQ的語音視頻測試向導的功能了。但是,我覺得語音測試這塊的體驗還可以做得更好一點,就像QQ語音測試一樣,實時顯示麥克風采集到的聲音的強度:
接下來,我們做個小demo,來實現類似的功能。先上demo運行起來的截圖:
(界面確實比較丑,我們這里的重點在于技術方面如何實現,如果你愿意花點時間,可以將其美化得跟QQ的那個一樣漂亮^_^)
1.實現思路
實現這個小例子的主要思路如下:
(1)使用OMCS采集和播放從麥克風的輸入數據(PCM)。
(2)對采集到的數據進行傅立葉變換,變換的結果就可以反應聲音的強度。
(3)使用ProgressBar控件來實時顯示聲音的強度信息。
2.具體實現
(1)傅立葉變換算法
public static class FourierTransformer { public static double[] FFTDb(double[] source) { int sourceLen = source.Length; int nu = (int)(Math.Log(sourceLen) / Math.Log(2)); int halfSourceLen = sourceLen / 2; int nu1 = nu - 1; double[] xre = new double[sourceLen]; double[] xim = new double[sourceLen]; double[] decibel = new double[halfSourceLen]; double tr, ti, p, arg, c, s; for (int i = 0; i < sourceLen; i++) { xre[i] = source[i]; xim[i] = 0.0f; } int k = 0; for (int l = 1; l <= nu; l++) { while (k < sourceLen) { for (int i = 1; i <= halfSourceLen; i++) { p = BitReverse(k >> nu1, nu); arg = 2 * (double)Math.PI * p / sourceLen; c = (double)Math.Cos(arg); s = (double)Math.Sin(arg); tr = xre[k + halfSourceLen] * c + xim[k + halfSourceLen] * s; ti = xim[k + halfSourceLen] * c - xre[k + halfSourceLen] * s; xre[k + halfSourceLen] = xre[k] - tr; xim[k + halfSourceLen] = xim[k] - ti; xre[k] += tr; xim[k] += ti; k++; } k += halfSourceLen; } k = 0; nu1--; halfSourceLen = halfSourceLen / 2; } k = 0; int r; while (k < sourceLen) { r = BitReverse(k, nu); if (r > k) { tr = xre[k]; ti = xim[k]; xre[k] = xre[r]; xim[k] = xim[r]; xre[r] = tr; xim[r] = ti; } k++; } for (int i = 0; i < sourceLen / 2; i++) { decibel[i] = 10.0 * Math.Log10((float)(Math.Sqrt((xre[i] * xre[i]) + (xim[i] * xim[i])))); } return decibel; } private static int BitReverse(int j, int nu) { int j2; int j1 = j; int k = 0; for (int i = 1; i <= nu; i++) { j2 = j1 / 2; k = 2 * k + j1 - 2 * j2; j1 = j2; } return k; } }
至于傅立葉變換與分貝有什么關系,網上有很多相關的資料,可以baidu一下。對有興趣的童鞋,強烈推薦閱讀這篇文章 -- 分貝是個什么東西?。
(2)初始化OMCS服務器、設備管理器、麥克風設備
//獲取麥克風列表 IList<MicrophoneInformation> microphones = SoundDevice.GetMicrophones(); this.comboBox2.DataSource = microphones; if (microphones.Count > 0) { this.comboBox2.SelectedIndex = 0; } //初始化OMCS服務器 OMCSConfiguration configuration = new OMCSConfiguration(10, 1, EncodingQuality.High, 16000, 800, 600); this.multimediaServer = new MultimediaServer(9000, new DefaultUserVerifier(), configuration, false, null); this.multimediaManager.DeviceErrorOccurred += new CbGeneric<MultimediaDeviceType, string>(multimediaManager_DeviceErrorOccurred); this.multimediaManager.AudioCaptured += new CbGeneric<byte[]>(multimediaManager_AudioCaptured); this.microphoneConnector1.ConnectEnded += new CbGeneric<ConnectResult>(microphoneConnector1_ConnectEnded);
(3)連接麥克風,開始采集
if (!SoundDevice.IsSoundCardInstalled()) { this.label_error.Visible = true; this.label_error.Text = "聲卡沒有安裝"; } //初始化多媒體管理器 this.multimediaManager.MicrophoneDeviceIndex = this.comboBox2.SelectedIndex; this.multimediaManager.Initialize("tester", "", "127.0.0.1", 9000); //與OMCS服務器建立連接,并登錄 //嘗試連接麥克風 this.microphoneConnector1.BeginConnect("tester");
首先,初始化本地多媒體設備管理器,然后使用麥克風連接器連接到當前登錄用戶“tester”(即“自己”)麥克風設備。如果連接成功,多媒體管理器將會觸發AudioCaptured事件,我們通過這個事件來截獲音頻數據。
(4)處理采集到的音頻數據,并顯示結果
void multimediaManager_AudioCaptured(byte[] data) { double[] wave = new double[data.Length / 2]; int h = 0; for (int i = 0; i < wave.Length; i += 2) { wave[h] = (double)BitConverter.ToInt16(data, i); //采樣位數為16bit ++h; } double[] res = FourierTransformer.FFTDb(wave); double kk = 0; foreach (double dd in res) { kk += dd; } if (kk < 0) { kk = 0; } this.showResult(kk / res.Length); } private void showResult(double rs) { if (this.InvokeRequired) { this.BeginInvoke(new CbGeneric<double>(this.showResult), rs); } else { int rss = (int)(rs * 2); if (rss < 40) { rss = 40; } if (rss > 100) { rss = 100; } this.progressBar1.Value = rss; } }
注意:由于OMCS音頻采樣的位數為16bit,這樣,一個單位的語音樣本的字節數為2個字節。所以,傅立葉變換前,先要將原始的PCM數據(byte[])轉為Int16的數組。
在顯示分貝強度時,我偷了下懶,直接使用了ProgressBar控件,體驗不是很好,勉強能表達出意思吧。
3.Demo程序
源碼下載。
文章列表