分貝顯示器,實時顯示聲音強度(附原始碼)

zhuweisky發表於2013-08-25

使用 攝像頭、麥克風、揚聲器測試程式 一文中提到的技術,我們可以基本實現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程式

    原始碼下載

 

相關文章