<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                [音頻傳輸之Jitter Buffer設計與實現](https://www.cnblogs.com/talkaudiodev/p/8025242.html) 在語音通信中Jitter Buffer(下面簡稱JB)是接收側一個非常重要的模塊,它是決定音質的重要因素之一。一方面它會把收到的亂序的語音包排好序放在buffer里正確的位置上,另一方面它把接收到的語音包放在buffer中緩沖一些時間使播放的更平滑從而獲得更好的語音質量。下圖是JB在接收側軟件框圖中的位置。 ![](https://img.kancloud.cn/28/96/2896cab4e86388d54b3fcb0d475b144c_711x95.png) 從上圖可以看出,從網絡上收到的語音包會放在JB里(這個操作叫做PUT),在需要的時候便從JB里取出來(這個操作叫做GET)解碼直到播放出來。JB有兩種模式:adaptive(自適應的)和fixed(固定的)。Adaptive是指buffer的大小可以根據網絡環境的狀況自適應的調整;fixed是指buffer的大小固定不變。自適應的模式實現難度大,要求高,fixed相對簡單,現在基本上都用adaptive的模式。JB在生命周期里也有兩種狀態:prefetching(預存取)和processing(處理中),只有在processing時才能從JB中取到語音幀。初始化時把狀態置成prefetching,當在JB中的語音包個數達到指定的值時便把狀態切到processing。如果從JB里取不到語音幀了,它將又回到prefetching。等buffer里語音包個數達到指定值時又重新回到processing狀態。 首先看PUT操作。RTP包有包頭和負載(payload),為了便于處理,將包頭和payload在buffer中分開保存,保存包頭中相關屬性的叫attribute buffer,保存payload的叫payload buffer。下圖是JB里存RTP包的buffer關系圖: ![](https://img.kancloud.cn/aa/58/aa58ee2dfd55cf888353986da2fc1e41_708x296.png) 要明確哪幾種類型的RTP包會被PUT進JB,我最初設計JB時類型有G711/G722/G729/SID(靜音包)/RFC2833(DTMF包)。 . G711/G722十毫秒payload是80個字節,G729十毫秒payload是10個字節,當VAD使能時十毫秒payload是2個字節(G729 VAD是內置的)或0個字節(DTX),一個SID包payload是1個或11個字節,一個RFC2833包payload是4個字節,明確這些是為了確定payload buffer中一個block的大小(取這些類型中最大的,80個字節),attribute buffer中一個block的大小是固定的,即要保存的屬性的個數(這些屬性主要用于控制payload的存放和讀取,有media type(G711/G722/G729/SID/RFC2833),sequence number,timestamp,ssrc,payload size,相對應的存放payload的buffer block指針等。 . 每個RTP的包頭占一個attribute buffer block,但每個RTP的payload有可能占幾個payload buffer block,這跟media type 和packet time有關,例如一個packet time為20ms的G711包,就需要兩個payload buffer block,attribute buffer block和payload buffer block之間有一個映射關系。將attribute buffer block和payload buffer block個數都定為256(index從0到255,設定256是為了早到的包絕不會把前面的包給覆蓋掉,如果block個數小了則有可能),這樣JB 里最少可以存2560ms的語音數據。? 至于JB里最多能放多少個包(即容量capacity),這取決于media type和packet time。如果media type是G711/G722, capacity = 256\*10/packet time,例如當packet time為20ms時,capacity是128,即最多放128個包。這樣attribute buffer和payload buffer的映射關系如下圖: ![](https://img.kancloud.cn/89/c9/89c9b91a00c32692161186eb2b834860_447x212.png) 如果media type是G729,考慮到packet time 通常不會超過160ms, 就設定一個G7299包的payload占2個block(160個字節,一般是存不滿的),這樣capacity就是128(256/2)。至于SID和RFC2833包,payload只有幾個字節,為了處理簡單,它們的payload占幾個block是跟著語音包走的,比如一個20ms的G711語音包payload占2個block,SID包和RFC2833包的payload也會占2個block。 . 從網絡上來的RTP包有可能是亂序的,PUT操作要把這些亂序的包(attribute & payload)放在buffer里正確的block里,這主要依靠attribute里的sequence number和timestamp做判斷。RTP協議里sequence number數據類型是unsigned short,范圍是0~65535,就存在從65535到0的轉換,這增加了復雜度。對于收到的RTP包,首先要看它是否來的太遲(相對于上一個已經取出的包),太遲了就要把這個包主動丟棄掉。設上一個已經取出的包的sequence number為 last\_got\_senq,timestamp 為last\_got\_timestamp,當前收到的將要放的包的sequence number為 cur\_senq,timestamp 為cur\_timestamp,當前包的sequence number與上一個取走的sequence number的gap為delta\_senq,則delta\_senq可以根據下面的邏輯關系得到。 ![](https://img.kancloud.cn/98/91/9891cd89e4f51b14026727bda4b3cdf4_690x344.png) 如果delta\_senq小于1,就可以認為這個包來的太遲,就要主動丟棄掉。由于我們的buffer足夠大(256個block),如果包早到了也會被放到對應的position上,不會把相應位置上的還沒取走的覆蓋掉。 . 接下來看怎么把包放到正確的位置上。對于收到的第一個包,它的位置(position,范圍是0 ~ capacity-1)是sequence number % capacity。后面的包放的position依賴于它上一個已放好的包的position。設上一個已放好的包的sequence number為 last\_put\_senq,timestamp 為last\_put\_timestamp,position為last\_put\_position,當前收到的將要放的包的sequence number為 cur\_senq,timestamp 為cur\_timestamp,position為cur\_position,當前的包的sequence number與上一個放好的sequence number的gap為delta\_senq,則cur\_position可以根據下面的邏輯關系得到。 ![](https://img.kancloud.cn/85/32/85329b88a7b3c305af14fd0874a37258_856x376.png) 得到了當前包的position后就可以把包頭里的timestamp等放到相應的attribute buffer block里了,payload根據算好的占幾個block放到相應的那幾個block上(有可能填不滿block,不過沒關系,取payload時是根據index取的)。如果放進對應block時發現里面已經有包了并且sequence number一樣,說明這個包是重復包,就要把這個包主動丟棄掉。 . 再來看GET操作。每次從JB里不是取一個包,而是取1幀(能編解碼的最小單位,通常是10ms,也有例外,比如AMR-WB是20ms),這主要是因為播放loop是10ms一次(每次都是取一幀語音數據播放)。取時總是從head上取,開始時head為第一個放進JB的包的position,每取完一個包(幾幀)后head就會向后移一個位置。如果到某個位置時它的block里沒有包,就說明這個包丟了,這時取出的就是payload大小就是0,告訴后續的decoder要做PLC。不同類型的包取法不一樣,下面分別加以介紹。 . 對于G711/G722,每次從payload buffer里取10ms數據(一個block, 80個字節),一個包取完后取下一個包。對于G729,每次從payload buffer里取10ms數據(10個字節或2個字節(VAD使能后的靜音payload)或0個字節(DTX)),一個包取完后取下一個包。至于VAD使能后取10個字節還是2個字節還是0個字節,要取決于當前包以及上一包的payload size。這處理好能顯著提高G7229 VAD使能場景下的語音質量MOS值。以packet time為20ms為例,如果上一個包的payload size是20個字節,當前包的payload size是12個字節,在取時前10ms取10個字節,后10ms取2個字節。如果上一個包的payload size是12個字節,當前包的payload size是10個字節,在取時前10ms取0個字節(DTX),后10ms取10個字節。 . 對于SID包,每次都是從當前包中取相同的payload一直到發現JB里這個SID包后面又有包并且timestamp又大于等于這個包的timestamp,下一次就會從這個新包里取payload。對于RFC2833包,包里有個duration attribute,當前RFC2833包和上一個RFC2833包的duration相減再除以80就是當前包的packet time,根據這算是從這個包里取得次數,次數到后就從下一個包取。 . 上面說過現在JB一般都是用adaptive的mode,即buffer size(緩存包的個數)根據網絡環境自適應的調整大小。那怎么來實現呢?JB初始化時會設定一個緩存包的個數值(叫prefetch),并處于prefetching狀態,這種狀態下是取不到語音幀的。JB里緩存包的個數到達設定的值后就會變成processing狀態,同時可以從JB里取語音幀了。在通話過程中由于網絡環境變得惡劣,GET的次數比PUT的次數多,GET完最后一幀就進入prefetching狀態。當再有包PUT進JB時,先看前面共有多少次連續的GET,從而增大prefetch值,即增大buffer size的大小。如果網絡變得穩定了,GET和PUT就會交替出現,當交替出現的次數達到一定值時,就會減小prefetch值,即減小buffer size的大小,交替的次數更多時再繼續減小prefetch值。 ***** 再來看一下在哪些情況下需要reset JB,讓JB在初始狀態下開始運行。 1)當收到的語音包的媒體類型(G711/G722/G729,不包括SID/RFC2833等)變了,就認為來了新的stream,需要reset JB。 2)當收到的語音包的SSRC變了,就認為來了新的stream,需要reset JB。 3)當收到的語音包的packet time變了,就認為來了新的stream,需要reset JB。 前面說過JB是語音通信接收側最重要的模塊之一,當然它也是容易出問題的模塊之一。出問題不怕,關鍵是怎么快速定位問題。對于JB來說,需要知道當前的運行狀態以及一些統計信息等。如果這些信息正常,就說明問題很大可能不是由JB引起的,不正常則說明有很大的可能性。這些信息主要如下: 1)JB當前運行狀態:prefetching / processing 2)JB里有多少個緩存的包 3)從JB中取幀的head的位置 4)緩沖區的capacity是多少 5)網絡丟包的個數 6)由于來的太遲而被主動丟棄的包的個數 7)由于JB里已有這個包而被主動丟棄的包的個數 8)進prefetching狀態的次數(除了第一次) . 上面就是JB設計的主要思想,在實現時還有很多細節需要注意,這里就不一一詳細說了。我第一次設計實現JB是在2011年,當時從設計實現到調試完成(指標是:bulk call > 10000次,long call time > 60 小時,各種場景下的各種codec的語音質量要達標)總共花了近三個月,還是在對JB有基礎的情況下,要是沒基礎花的時間更多。從設計到能打電話時間不長,主要是后面要過bulk call/long call/voice quality。有好多情況設計時沒考慮到,這也是一個迭代的過程,當調試完成了設計也更完整了。最初設計時只支持G711/G722/G729這三種codec,但是機制定了。后來系統要支持AMR-WB,JB這部分根據現有的機制再加上AMR-WB特有的很快就調好了。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看