Android的音頻系統的代碼中,應用程式對每個音頻流的音量做出調整後,最終會轉換為一個係數K,所有的音頻資料在輸出到硬體之前,都要乘以係數K,只要應用程式發出調整音量的調用,中介層的Audio System就會重新計算係數K的值。對應用程式來說,音量控制通常都是按照線性進行調整的,比如對於具有15級音量的音頻流來說,我們預期每級的音量變化都是相當的,也就是說:從第5級調到第6級,和從第7級調到第8級,我們期望人耳可以感覺到同樣大小的音量變化。但是,在Android的代碼中,我們看到了計算係數K的公式,它相當奇怪,代碼位於frameworks/base/media/libmedia/audiosystem.cpp中:
/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/
// convert volume steps to natural log scale// change this value to change volume scalingstatic const float dBPerStep = 0.50f;// shouldn't need to touch thesestatic const float dBConvert = -dBPerStep * 2.302585093f / 20.0f;static const float dBConvertInverse = 1.0f / dBConvert;float AudioSystem::linearToLog(int volume){ // float v = volume ? exp(float(100 - volume) * dBConvert) : 0; // LOGD("linearToLog(%d)=%f", volume, v); // return v; return volume ? exp(float(100 - volume) * dBConvert) : 0;}int AudioSystem::logToLinear(float volume){ // int v = volume ? 100 - int(dBConvertInverse * log(volume) + 0.5) : 0; // LOGD("logTolinear(%d)=%f", v, volume); // return v; return volume ? 100 - int(dBConvertInverse * log(volume) + 0.5) : 0;}
要理解上面代碼中的公式,我們先要瞭解人耳的聲心理學模型。根據人耳的聲心理學的研究,人耳對聲音大小的感知程度並不是線性,而是呈對數關係。對數形式的單位是dB,在音頻領域,通常我們會定義一個標準電平V0,那麼電平X的轉換公式是:
dB=20log(X/V0);
例如:我們給喇叭輸出滿負荷最大音量時的電平是1V,如果有15級音量,如果按線性進行調整,1/15 = 66.6mV,我們就得到每級音量的調整量是:
66.6mV,133.2mV,200mV,......,866.8mV,933.4mV,1000mV;
如果按照這個步長進行調整,人耳感覺到的音量變化就不是連續的。
另一種方式是按對數進行調整,在數字音頻領域,通常0dB代表最大音量,0dB意味著不對資料進行任何的變換處理,輸出等於輸入,所以20log(V0/V0)=20log(1)=0dB。這意味著最大音量以下的dB值為一個負數,現在我們把1V認為是0dB,最低音量是-28dB,那麼對應15級音量的dB值就是:
-28dB,-26dB,-24dB,......,-4dB,-2dB,0dB;
對應的電平值是( 使用公式Vx=10^(dB/20)*V0 ):
39mV,50mV,63mV,......,630mV,794mV,1000mV;
線性音量和對數音量的調整曲線
回到Android的代碼中,它也使用了對數的調節方式,它先是定義了每次調節音量的步長值為0.5dB:
static const float dBPerStep = 0.50f;
然後他定義了一個計算用的中間常數:
static const float dBConvert = -dBPerStep * 2.302585093f / 20.0f;
這個一開始有點難於理解,尤其是奇怪的係數:2.302585093。所有這些定義都是為了得到用於與音頻資料相乘的係數K,Android中有多種音頻流,每種音頻流的預設音量大小步數都不一樣,有的是7步,有的是5步,有的是15步,為了便於計算的統一,計算前都會先把相應的步數映射為0-100步之間,因為步長已經定義為0.5dB,所以各級音量對應的dB數如下:
| 音量層級 |
0 |
1 |
2 |
3 |
...... |
97 |
98 |
99 |
100 |
| dB數 |
mute |
-49.5dB |
-49dB |
-48.5dB |
...... |
-1.5dB |
1.0dB |
0.5dB |
0dB |
很顯然,知道了音量為哪個步數層級後,相應的dB值也會知道,那麼我們要做的就是把dB值轉換為係數K值,K值實際上就是公式dB=20log(X/V0)中的比值:X/V0,根據此公式反推,音量層級為volume對應的K值:
(1) dB = -dBPerStep * ( 100 - volume );
又因為:
(2) dB/20 = log(Vx/V0) = log(K);
把(1)式代入(2)式:
(3) -dBPerStep * ( 100 - volume ) / 20 = log(K);
為了得到K,兩邊取以10為底的指數:
(4) 10 ^ ( -dBPerStep * ( 100 - volume ) / 20 ) = 10 ^ ( log(K) );
(5) K = 10 ^ ( log(K) ) = 10 ^ ( -dBPerStep * ( 100 - volume ) / 20 ) ;
(6) K = 10 ^ ( dBConvert * ( 100 - volume ) ) ; // 令:dBConvert = -dBPerStep / 20;
使用(6)式即可得到係數K,需要計算以10為底的冪,可是這與Android使用的計算公式有些差異,Andrioid使用的公式是:
(7) exp(float(100 - volume) * dBConvert);
這是因為它沒有使用以10為底的冪運算,而是使用以自然常數e為底的冪運算,因為:
(8) ln( 10) = 2.302585093;
我們把dBConvert 重新定義為-dBPerStep * 2.302585093/ 20後,式子(6)和式子(7)實際上是完全等價的。也就是說:
(9) e^2.302585093 = e^ln(10) = 10;
這下終於知道2.302585093這個奇怪數位來曆啦!!從代碼的注釋中,我們可以知道,只要改變dBPerStep的大小,就可以決定系統的最小音量了:
最小音量 = -99 * dBPerStep;預設情況下是-49.5dB,K值為:0.00334965439;
至於最大軟體數字音量,就是0dB,不能改變,要改就修改底層的音頻驅動的硬體音量吧!!