我在前面的文章(Android智能手機上的音頻淺析)中說過Android手機上有一塊專門用於音頻處理的DSP,它的特點是頻率低(一般幾百MHZ)、內部memory小(通常不超過100k word)。要想讓Audio DSP上放下更多的內容以及能流暢的運行,要有一些應對措施。今天就聊聊這些措施。
1,頻率低的應對措施
由於DSP的頻率低,要想軟件能流暢的運行,就得把運行時的load降下來。主要的措施有兩種,定點化和load優化。先看定點化。
DSP有定點DSP和浮點DSP之分。一般來說,定點DSP具有速度快,功耗低,價格便宜的特點;而浮點DSP則計算精確,動態范圍大,速度快,易於編程,功耗大,價格高。音頻處理用的DSP通常都是定點DSP。定點DSP,處理定點數據會相當快,但是處理浮點數據就會非常慢。那在定點DSP上涉及到浮點運算怎么辦呢?解決方法是浮點運算定點化來節約處理時間,即用定點數表示浮點數。定點數中最高位表示符號位,符號位右邊n位表示整數,剩下的表示小數。這種表示方法叫Q格式。
Q格式表示為:Qm.n,表示數據用m比特表示整數部分,n比特表示小數部分,共需要m+n+1位來表示這個數據,多余的一位用作符合位(不表示出來)。例如Q15表示小數部分有15位,一個short型數據,占2個字節,最高位是符號位,后面15位是小數位,表示的范圍是:-1<X<0.9999695 。浮點數據轉化為Q15,將數據乘以2^15;Q15數據轉化為浮點數據,將數據除以2^15。例如假設數據存儲空間為2個字節,0.333×2^15=10911=0x2A9F,0.333的所有運算就可以用0x2A9F表示,同理10911×2^(-15)=0.332977294921875,可以看出浮點數據通過Q格式轉化后是有誤差的。
再看看Q格式的加減乘除基本運算。加減法時須轉換成相同的Q格式才能加減。不同Q格式的數據相乘,相當於Q值相加,要右移n位得到正確的值。不同Q格式的數據相除,相當於Q值相減,要左移n位得到正確的值。這里舉一個乘的例子:兩個小數相乘,0.333*0.414=0.137862
0.333*2^15=10911=0x2A9F,0.414*2^15=13565=0x34FD
short a = 0x2A9F;
short b = 0x34FD;
short c = a * b >> 15; // 兩個Q15格式的數據相乘后為Q30格式數據,因此為了得到Q15的數據結果需要右移15位
這樣c的結果是0x11A4=0001000110100100,這個數據同樣是Q15格式的,它的小數點假設在第15位左邊,即為0.001000110100100=0.1378173828125...和實際結果0.137862差距不大。或者0x11A4 / 2^15 = 0.1378173828125
其他的基本運算就不舉例了,網上講Q格式的有一些文章,感興趣自己去看。
實際應用中,浮點運算大都時候都是既有整數部分,也有小數部分的。所以要選擇一個適當的定標格式才能更好的處理運算。一般用如下兩種方法:一是使用適中的定標,既可以表示一定的整數復位也可以表示小數復位,如對於2812的32位系統,使用Q15格式,可表示-65536.0~65535.999969482區間內的數據。二是全部采用小數,這樣因為小數之間相乘永遠是小數,永遠不會溢出。取一個極限最大值(最好使用2的n次冪),轉換成x/Max的小數(如果Max是取的2的 n次冪,就可以使用移位代替除法)。剛開始用Q格式時會很別扭,不習慣,用多了就慢慢習慣了。
為了降load,我們在軟件開發過程中要做到以下兩點:一是選擇算法實現時一定要用定點實現。Audio DSP上會運行好多音頻處理算法,比如各種codec,這些codec的制定者一般會提供兩套reference code,一套定點實現的,一套浮點實現的,我們在選擇時一定要選用定點實現的。對於應用在ARM上的音頻處理算法,同樣是推薦用定點實現的算法,雖然ARM頻率高。在ARM上還是要優化算法把load降到盡量低。二是在我們自己的代碼中如遇到要做浮點運算(比如算百分比),要用Q格式定點化去算,而不是直接用浮點數去運算。
再來看load優化。我在前面的文章(音頻的編解碼及其優化方法和經驗)中load優化的一些通用方法,在DSP上也適用。不過由於DSP頻率低,且DSP上運行的音頻處理算法多,有些算法又比較耗load(比如AEC),要想讓音頻軟件流暢的運行,復雜的算法都是要做匯編優化的。做這些不僅專業而且耗時,load優化沒有最低只有更低。
2,memory低的應對措施
DSP的內部memory分兩種,DTCM(Data Tightly Coupled Memory, 數據緊密耦合存儲器)和PTCM(Program Tightly Coupled Memory, 程序緊密耦合存儲器)。DTCM用於存data,PTCM用於存code。同時還有外部memory(DDR),它也可存data和code。data和code盡量放內部,因為這樣速度快效率高,不到萬不得已才將它們放在外部memory上。即使放在外部的也是一些低頻訪問的data和code,如初始化函數等。今天我們主要講的是應對DTCM(data memory)小的措施。DTCM主要分以下幾個區域:const區、data區、bss區、overlay區。const區顧名思義就是放一些常量的,data區是放已初始化的全局變量和靜態變量,bss區是放未初始化的全局變量和靜態變量,overlay區主要是根據場景的互斥做一些memory的復用,這是應對memory小的主要措施。我們先看overlay機制。
Overlay機制是指不會同時發生的場景下使用的memory可以復用,在音頻上主要有兩種情況。一是music使用的memory可以和voice使用的memory復用,因為音樂播放和打電話不可能同時發生。通常是音樂播放時來電話,音樂播放就暫停,把音樂播放的context以及還在buffer中未播放的數據拷貝到外部memory上,把music使用的memory讓給voice用。打電話結束后再把放在外部memory上的音樂相關的context以及未播放的數據拷進內部memory原先的位置上,從而繼續音樂的播放。二是各種codec使用的memory的復用,因為同時只有一種codec在使用。音樂的decoder有MP3、AAC,播放音樂時只可能有一種decoder在用。Voice的codec有AMR-NB、AMR-WB、EVS,打電話時只可能有一種codec在用。它與第一種的區別是不需要保存上下文。這樣可以畫出DTCM的分布圖,如下圖:

上圖中memory地址是由低向高增長,分別是const區、data區、bss區、overlay區。在overlay區music和voice有相同的起始地址,在music中decoder MP3和AAC又有相同的起始地址,在voice中codec AMR-NB、AMR-WB和EVS又有相同的起始地址。
對於PTCM,同樣可以用overlay機制。不過除非一些代碼重寫,一般很難省code size了。
除了overlay機制,其他的就要一點一點的摳來省memory了,主要有以下幾點:
1) 定義數據類型時能用short的就不要用int
2)在overlay區域,buffer的大小都是指定的。指定時要正確算出大小值,不要指定大了,指定大了就浪費了。
3)在data區或者bss區的buffer要看是不是分大了,比如有的buffer分三塊就夠了,也就沒必要分四塊了,分四塊一是浪費了buffer,二是有些場景下增加了時延。
4) DSP上每個thread/task的棧的大小都是指定的。為了省memory,棧的大小不可能很大,一般不超過1k word。這就要求寫代碼時不能有大的局部變量數組等,遇到時就要通過一些技巧解決。如一個要把雙聲道的數據從interleave變成non-interleave的函數,寫成了如下實現,避免了大的局部變量數組。通常的做法是用一個大的局部變量數組先存右聲道數據,最后再一起拷到指定位置上。
void interleave_to_noninterleave(int16_t *buf, int32_t frame_cnt)
{
int i,j;
short temp;
for(i = 1; i < frame_cnt; i++) {
temp = buf[2*i];
for(j = 0; j < i; j++)
buf[2*i - j] = buf[2*i - j -1];
buf[i] = temp;
}
}
5)代碼編好后會生成一個各buffer起始地址和大小的文件。關注那些size較大的buffer,分析有沒有減小的可能。
總體而言,DSP由於頻率低和memory小的限制,在上面寫代碼比在ARM上要求高些,花的時間長些。長時間在ARM上寫代碼,轉到DSP上會有些不習慣,有個適應過程。
