声波转字符c语言代码,声波传输解码
這篇文章是系列文章的第3篇。
第一篇在這里聲波配網(wǎng)原理。
關(guān)于聲波傳輸編碼的部分在這里聲波傳輸編碼。
前面說過,聲波傳輸?shù)倪^程可以理解成對稱加解密的過程,因而和聲波傳輸編碼的三個步驟對應,聲波傳輸解碼也分為下面這樣的三步:1.從正弦波音頻信號到對應的頻率數(shù)組
2.根據(jù)頻率數(shù)組反映射到數(shù)字數(shù)組
3.根據(jù)碼表查找對應的字符
首先我們先來看看最復雜的這步:從正弦波音頻信號到對應的頻率數(shù)組。這一步涉及的操作比較多,寫代碼的時候容易,整理成文章還是有點麻煩,我盡量讓我的敘述有邏輯,成系統(tǒng)。
下面的6個點基本囊括了這一步要做的事情。1.音頻信號的采集
2.開始音節(jié)
3.快速傅里葉變換
4.解析窗口大小的選取
5.對齊音節(jié)
6.順序解析
7.結(jié)束音節(jié)
1.音頻信號的采集
這個要根據(jù)自己的需求來定,選擇不同的平臺做解碼的工作,音頻信號的采集就在相應的平臺完成。如果是Android 平臺的寶寶的話,用 AudioTrack 就可以了。當發(fā)送端播放聲音的時候,是在源源不斷的產(chǎn)生音頻信號的,而解碼端在音頻信息中解析到我們約定好的開始音節(jié)頻率之后,要進行一些操作(需要在開始標志位置附近來回掃描計算確定最佳開始解析的位置),可以形象地把它稱為對齊音節(jié)。在對齊音節(jié)的過程中,聲音產(chǎn)生的速率是遠遠大于我們消費聲音的速率的,而一旦我們對齊音節(jié)之后,開始音節(jié)之后的音節(jié)就是攜帶信息的音節(jié),因而在我們沒有處理完開始音節(jié)的時候,下一個音節(jié)的數(shù)據(jù)既不能丟棄,也不能處理。
這種情況很明顯就需要用到經(jīng)典的生產(chǎn)者消費者模式來平衡錄制音頻的線程和解析線程之間的速率,防止丟失數(shù)據(jù)(緩沖區(qū))。
而關(guān)于錄制時采樣率,聲道數(shù),數(shù)據(jù)位數(shù)的選取都應該和編碼端一致。
2.開始音節(jié):
在聲波配網(wǎng)的原理篇,我們說過,把一段攜帶一個字符信息的單頻率的正弦波叫做一個音節(jié)。而開始音節(jié)的作用是告訴解碼端,聲波已經(jīng)開始播放,這個音節(jié)結(jié)束之后的音頻信號就是攜帶信息的聲波,可以開始解碼。我們需要定一個閾值,當開始頻率的振幅超過一定的閾值就認為識別到了音頻的開始位置,可以開始解碼。
3.快速傅里葉變換
快速傅里葉變換的作用在于從一個解析窗口(緩沖區(qū))中解析出里面的音頻信號包含的頻域信息。快速傅里葉變換的準確率和效率直接影響著我們解碼成功的準確率和效率,因而推薦使用經(jīng)典的庫 Mark Borgerding 的 kiss_fft 。然后將我們關(guān)心的頻率【編碼時采用的98個頻率】的振幅大小記錄下來后續(xù)進行分析。
但我們要考慮到一點,我們在利用快速傅里葉變換計算頻率的時候,實際上是根據(jù)相位得到周期,然后再得到頻率,先不說計算機關(guān)于浮點數(shù)的存儲就是近似值。就算是能精確存儲浮點數(shù),通過2*pi/T計算來的頻率也是一個在我們真正的頻率附近的值。因而如果我們的目標頻率是1700Hz,那么1699,1701也應該看作是音頻信號中有我們的目標頻率的標志,這個左右挪動的范圍究竟要定成多大了?看心情哈哈哈~~~
改動這個參數(shù),看這個參數(shù)改變后的具體效果,可以自定。
蠢作者定的是15,以1700為目標頻率的話,傅立葉變換解析出的1685-1715之間的頻率,我都認為是目標頻率。這個閾值影響著我們在編碼時對各個字符對應的頻率設定,定成15意味著相鄰兩個字符對應的頻率間隔至少要大于30。
4.解析窗口大小的選取:
由于每個音節(jié)持續(xù)時間內(nèi),一個正弦波信號都重復了很多個周期,所以當周期重復的次數(shù)(包含的采樣點的多少)達到一定值后,并不影響快速傅里葉變換計算出的振幅大小。(理論上如果采樣點是連續(xù)而不是離散的,能完整的還原聲音信息,只要包含正弦波信號的一個周期,那么計算出的振幅就是固定的。)但由于我們在實際中是用離散的采樣點記錄聲音信息,而且聲波在從發(fā)送端設備播放,到在空氣中傳輸,再到接收端設備接收,中間還有環(huán)境噪音的影響。如果重復的周期數(shù)太少,計算出的頻域信息就會是不準確的。
網(wǎng)上流傳的解決方案,都選擇了讓解析窗口略大于編碼的音節(jié)大小。這也是導致解析了一段音頻后,解碼成功率就會下降的根本原因。
讓解析窗口大于音節(jié)窗口的原因很簡單,這樣解析窗口就能包括一個音節(jié)的數(shù)據(jù),通過傅里葉變換出來的結(jié)果就是準確的。而由于解碼的時候,我們開始解析的位置其實是隨機的,所以在開始音節(jié)過后,正式解析數(shù)據(jù)的時候會出現(xiàn)下面三種常見的開始解析的位置(解析窗口的一端剛好卡在兩個音節(jié)之間的情況可以隨機的分到這三種情況中):
可以看到當前解析窗口的數(shù)據(jù)中除了包含當前音節(jié)的數(shù)據(jù)以外,還包含上一個音節(jié)的數(shù)據(jù)和下一個音節(jié)的數(shù)據(jù)。
反過來思考就是,當前位置我們想要的字符的音節(jié)數(shù)據(jù)最可能的情況是,大部分在當前解析窗口中,但也會有一部分在上一個解析窗口中或者下一個解析窗口中。
所以這種方式下,我們從音頻數(shù)據(jù)中選取頻率的時候就不是選擇出振幅最大的那么簡單。
圖1
這是由 energy 公式生成的單頻率的正弦波的頻譜分析,因為是單頻率的,所以只需要找到振幅最大的頻率就可以。
但由于我們的解析窗口比音節(jié)長度要大,就沒有辦法保證目標頻率是最高的,要選出目標頻率,就需要定義很多的規(guī)則,比如振幅大于一定閾值的頻率,我們認為它在解析窗口中存在,比如圖1中的頻譜圖,只要是大于15DB的頻率,我們都認為它存在。然后一個頻率在上一個解析窗口中存在了,在這個解析窗口中又存在,那么它很可能是目標頻率。
對應著這樣的解析位置:
當然也有可能是這樣:
顯然這個選出目標頻率的規(guī)則會是很復雜的,要跨3個音節(jié)判斷,要根據(jù)不同的優(yōu)先級設定權(quán)值,越是復雜的東西越容易出錯。
當然規(guī)則定好了,還是可以識別的。那為什么會出現(xiàn)長度的限制問題了?
即使在我們開始解析的時候,解析窗口的起始點卡在了一個完美的位置:
但因為解析窗口比音節(jié)數(shù)據(jù)要大,向前滑動的時候這個偏移也會一直變大,長度夠長的時候,就會出現(xiàn)這樣的情況(必然會走入這樣的情況):
解析窗口中的數(shù)據(jù)一半來自前一個音節(jié),一半來自后一個音節(jié)。
這種情況下得到的傅里葉頻譜是完全沒有辦法區(qū)分出哪個音節(jié)是前面那個,哪個音節(jié)是后面那個的,這個位置就是一個只能靠運氣解析的位置。
同時這個解析窗口之前的狀況是每個解析窗口都是前面音節(jié)的數(shù)據(jù)占比大,這個解析窗口之后就會是后面那個音節(jié)的數(shù)據(jù)占比大。所以在這個位置之后開始解析的時候還會漏掉一個音節(jié)。認為這個窗口里的頻率是N,就會漏掉N+1,認為是N+1,就會漏掉N。
當然有的同學看到這里可能會說解析窗口定大一點,但是滑動的時候只滑動音節(jié)的大小不就可以解決這個問題了么?這樣做雖然能夠解決傳輸一定長度后出現(xiàn)的運氣位置和漏掉音節(jié)的情況,但是還是不能解決我們開始解析的位置具有隨機性的這個問題。如果我們開始解析的位置是這個樣子了:
說了這么多,蠢作者想表達的就是解析窗口比一個音節(jié)長度大就會出現(xiàn)這樣的一些比較難搞的問題,如果能搞定這幾個問題,應該也會有不錯的效果,不過分析到這里,蠢作者也就換了個思路,就是下面要說的對齊音節(jié)的方式。
解析窗口比音節(jié)數(shù)據(jù)要小,寶寶們類比上面的分析稍微分析一下就可以知道更加的不可行。
5.對齊音節(jié):
回顧我上面舉的例子,如果我們開始解析的位置能夠是這樣剛好卡在音節(jié)開始的地方,那解析的過程就會很容易了,每次都從窗口里找出振幅最高的頻率就可以了。
那要怎么做到讓解析位置卡在我們想要的地方了?
在正式的開始介紹對齊音節(jié)的方式之前,我們先看一個小實驗。
實驗的目的是分析解析窗口和音節(jié)錯開到不同位置時,由快速傅里葉變換計算出的振幅大小變換規(guī)律。生成3個音節(jié)的 PCM 文件(44100Hz采樣率,0.1s持續(xù)時間,每個音節(jié)4410個采樣點),第一個音節(jié)是攜帶頻率333Hz的正弦波,第二個音節(jié)是攜帶開始標志頻率的正弦波,第3個音節(jié)還是攜帶頻率333Hz的正弦波。以十分之一的偏差粒度分析從左到右掃描開始音節(jié)時開始標志頻率振幅的變化。
表1
看表說話,以下2個結(jié)論比較重要:
1.即使只包含了十分之一的開始音節(jié)信息,振幅也有0.544862,所以如果解析窗口和音節(jié)沒有對齊的話,即使是十分之一的位置偏差,包含的相鄰音節(jié)對當前音節(jié)也有很大的干擾;
2.在解析窗口左右偏移五分之一的時候計算出的振幅和完全對齊計算出的振幅沒有差別。
這2個結(jié)論也是讓我放棄解析窗口比音節(jié)長度大的原因,包含少量的其他音節(jié)數(shù)據(jù)也會產(chǎn)生比較大的干擾。
分析這個表還可以得出一個重要的結(jié)論:
解析窗口從左到右劃過目標音節(jié)的時候,快速傅里葉變換計算出的目標音節(jié)的振幅是先變大后變小的,在保持最大振幅不變的幾個位置的中間位置是最靠近完全對齊音節(jié)的位置的。
因此當我們的開始音節(jié)除了標志音頻開始以外,又多了個重要的作用,我們識別到了開始音節(jié)的頻率之后,可以通過來回掃描,找到開始音節(jié)振幅最大的位置,這個位置就應該在音節(jié)對齊的位置附近了。
具體的操作就是:
如果經(jīng)過傅里葉頻譜分析,當前傳入的解析窗口中有開始音節(jié)頻率,則把當前窗口的數(shù)據(jù)拷貝一份,和傳入的下一個窗口的音頻數(shù)據(jù)拼接起來,從左到右以一個解析窗口大小掃描,掃描的起始位置每次滑動 interval ?(90)個采樣點。這樣兩個窗口在一趟掃描中需要解析50次。
PS:50 = unit_sample/interval(49)+1,(49*90=4410)。
將掃描到的開始頻率的振幅值和上次掃描到的開始頻率的振幅值比較,如果當前振幅值小于了上一次的振幅值,說明已經(jīng)滑過了音節(jié)對齊的位置,對齊位置要從當前掃描位置回退(上個振幅出現(xiàn)的次數(shù)+1)/2*interval 個采樣點,這樣就找到了音節(jié)對齊的位置。
以表1中的情況為例,從左到右掃描到+3位置的時候,是第一次當前振幅小于上一次振幅的位置,說明滑過了對齊位置,而上一個振幅(也就是最大振幅0.808161)出現(xiàn)了5次,那么要回退(5+1)/2個interval(表1的 interval 是441),才是對齊后的開始音節(jié)位置。
好了,到這里開始音節(jié)對齊方式我們已經(jīng)介紹的差不多了,剩下還要考慮一些意外的情況。
在實際中傳入的音頻數(shù)據(jù)是從環(huán)境中錄制得到的,所以可能會有噪音干擾,導致還沒有滑到對齊位置的時候,由于噪音導致的當前掃描位置比上一個掃描位置振幅要小,這種情況下結(jié)束掃描會導致對齊的效果太差,為了避免偏離太遠,可以在結(jié)束對齊的時候多加一個判斷條件,當今掃描位置要大于一定的閾值 HEADERTHRESHOLD ,比如0.65,0.7之類的。這樣可以避免在離對齊位置比較遠時出現(xiàn)的偶然的振幅值小于上一個振幅值的情況。
關(guān)于 HEADERTHRESHOLD 的選定,蠢作者想啰嗦兩句,將這個值調(diào)大,可以更高程度的使得對齊位置準確,但是隨著播放音頻的設備離錄音設備的距離變遠,所有音頻的振幅都會下降,導致解碼端無法識別到開始音節(jié)。HEADERTHRESHOLD 越大,能識別的播放設備和錄音設備物理距離就越小。所以這個值的設定需要我們在準確率和識別距離上做出相應的取舍。
6.順序解析
開始音節(jié)對齊之后,后面就只需要順序解析每個窗口的音頻數(shù)據(jù)就可以啦。
在0~95個數(shù)據(jù)頻率區(qū)中選出振幅最大的頻率f,依次存入頻率數(shù)組 frequency。
7.結(jié)束音節(jié)
就像需要一個頻率標識音頻開始播放一樣,我們需要一個標志本次傳輸結(jié)束的頻率。識別到結(jié)束頻率后,意味著此次解碼的第一步,從正弦波音頻信號到對應的頻率數(shù)組的解析就完成了。
阿彌陀佛~~~終于把這個部分寫完了~~~
剩下的兩個映射過程:
2.根據(jù)頻率數(shù)組映射到數(shù)字數(shù)組;
3.根據(jù)碼表查找對應的字符;
由于這篇文章已經(jīng)很長了,我就不啰嗦了,就是查數(shù)組,查碼表,找到事先約定的頻率數(shù)組中各個頻率對應的字符返回,就完成了整個解析的過程。
最后總結(jié)一下,整個解碼的過程可以用下面這個很丑的狀態(tài)機概況:
解碼算法(狀態(tài)機 START,ALIGNMENTHEADER,DECODE):
1.在 START 狀態(tài),對上層傳入的設備錄入的一個解析窗口的音頻數(shù)據(jù)(解析窗口大小:采樣率*音節(jié)持續(xù)時間*每個采樣點字節(jié)數(shù)*聲道數(shù)),通過快速傅里葉變換分析其中是否有開始標志頻率,沒有的話這部分數(shù)據(jù)直接丟棄;有的話,可能是音頻開始的標志,將這個窗口數(shù)據(jù)拷貝一份保存,進入 ALIGNMENTHEADER 狀態(tài);
2.在 ALIGNMENTHEADER 狀態(tài),將傳入的一個窗口的音頻數(shù)據(jù)和保存的上一個窗口的音頻數(shù)據(jù)拼接起來,從左到右以一個解析窗口大小掃描,掃描的起始位置每次右移 interval 個采樣點,兩個窗口總共掃描 unit_sample/interval+1 次,將掃描到的開始頻率的振幅值和上次掃描到的開始頻率的振幅值比較,如果當前振幅值大于了 HEADERTHRESHOLD,并且小于了上一次的振幅值,說明已經(jīng)滑過了音節(jié)對齊的位置,對齊位置要從當前掃描位置回退(上個振幅出現(xiàn)的次數(shù)+1)/2*interval 個采樣點,然后進入到 DECODE 狀態(tài);否則的話,識別開始音節(jié)失敗,重新進入 START 狀態(tài)。
3.在 DECODE 狀態(tài),從對齊后的開始音節(jié)的下一個窗口開始,依次讀入窗口數(shù)據(jù),通過快速傅里葉變換分析出每一個窗口中的頻域信息,記錄我們關(guān)心的98個頻率的振幅,直到遇到傳輸結(jié)束標志,停止讀入數(shù)據(jù),計算并返回解析結(jié)果。
1)在0~95個數(shù)據(jù)頻率區(qū)中選出振幅最大的數(shù)字a;
2)查詢數(shù)字a對應的字符返回;
下一篇是聲波傳輸系列的最終篇啦,關(guān)于一些繼續(xù)優(yōu)化的想法。
總結(jié)
以上是生活随笔為你收集整理的声波转字符c语言代码,声波传输解码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: scheme 微博_可用App URL
- 下一篇: pycharm python 模板配置_