1.1 問(wèn)題提出Mix的意思是混音,無(wú)論在自然界,還是在音頻處理領(lǐng)域這都是非常普遍的現(xiàn)象。自然界里你能同時(shí)聽(tīng)到鳥(niǎo)鳴和水聲,這是因?yàn)轼B(niǎo)鳴和水聲的波形在空氣中形成了疊加,耳朵聽(tīng)到后能區(qū)分鳥(niǎo)鳴和水聲這兩種波形。 在數(shù)字音頻領(lǐng)域也是一樣,比如你也可以一邊打CS一邊聽(tīng)歌,這是因?yàn)橛?jì)算機(jī)把兩個(gè)聲音波形做了疊加。但是不同的是,計(jì)算機(jī)中的疊加,很容易造成越界。 比如 int plus1(int num0, int num1){ return num0+num1; } 如果賦值int num0=0x70000000和int num1=0x70000000,運(yùn)行后的result是0xE0000000,變換為十進(jìn)制為-536870912。兩個(gè)正數(shù)相加得到了負(fù)數(shù),結(jié)果自然是錯(cuò)的。 我們知道,一個(gè)char的補(bǔ)碼所能表示的數(shù)值范圍是[-128, 127],寫成16進(jìn)制是[0x80,0x7F]。而一個(gè)int的補(bǔ)碼的范圍是[0x80000000,0x7FFFFFFF]。超出這個(gè)范圍就是溢出。 如何防止溢出呢?最簡(jiǎn)單的做法是拓寬存儲(chǔ)數(shù)據(jù)的容器,比如: long long plus1(int num0, int num1){ return (long long)num0+(long long)num1; } 賦值int num0=0x70000000和int num1=0x70000000,運(yùn)行后的result是0xE0000000,變換為十進(jìn)制為3758096384。這次沒(méi)有溢出。 1.2 公式怎么能做到不溢出呢?考慮這個(gè)公式 Z=A+B?AB, 如果A和B都在[0,1]范圍內(nèi),那么: 0<=(1-A)(1-B)=1-A-B+AB<=1,那么 0<=Z<=1 這樣,如果我們把A,B看做是兩個(gè)輸入波形,Z看做是一個(gè)輸出波形的話,Z的上界和下界也在A和B的上界和下界內(nèi)。也就是說(shuō),Z是不會(huì)溢出的。 對(duì)于3個(gè)輸入信號(hào)來(lái)說(shuō),按照(1-A)(1-B)(1-C)運(yùn)算,易得 Z=A+B+C?AB?AC?BC+ABC. 而對(duì)于取值范圍不在[0,1]的信號(hào),可以先轉(zhuǎn)化為[0,1]來(lái)做。 比如A,B均在[0,255]范圍內(nèi),則A/255在[0,1]內(nèi),則 Z/255=A/255+B/255-(A/255)*(B/255),那么 Z=A+B-AB/255 對(duì)于有符號(hào)的數(shù),取值范圍在[-128,127],則A’=(A+128)/255取值在[0,1]內(nèi),則 Z’=A’+B’?A’*B’,代入可得 (Z+128)/255=(A+128)/255+(B+128)/255-(A+128)/255*(B+128)/255,則 Z=A+B-(A+128)(B+128)/255+128 這種算法可以認(rèn)為是簡(jiǎn)單的對(duì)輸入信號(hào)進(jìn)行了相加,并為了避免溢出,壓縮了兩個(gè)信號(hào)的和的波形。但是這種算法有個(gè)致命的缺點(diǎn),那就是當(dāng)兩個(gè)信號(hào)相加沒(méi)有溢出時(shí),這種算法仍然壓縮了波形,導(dǎo)致音質(zhì)受損。而且過(guò)多的加減乘除的運(yùn)算,會(huì)提升整個(gè)系統(tǒng)的功耗和復(fù)雜性,也會(huì)在四舍五入中降低數(shù)據(jù)的精度。 說(shuō)句題外話,為了避免運(yùn)算中聲音信號(hào)精度的丟失,目前業(yè)界高端音頻處理系統(tǒng)里都是用32位float采樣來(lái)進(jìn)行運(yùn)算的,而輸出的時(shí)候轉(zhuǎn)化為16bit。 1.3 Android做法我們看看成熟的軟件是怎么做的。Android的Mixer在AudioMixer.cpp這個(gè)文件里,它針對(duì)不同的情況,有各種執(zhí)行混音操作的函數(shù),下面這個(gè)函數(shù)是處理無(wú)需重采樣的立體聲音頻的。 voidAudioMixer::process__genericNoResampling(state_t* state, int64_t pts) 我們來(lái)看看它的處理方式:它是把各個(gè)track的聲音數(shù)據(jù)相加。所謂聲音數(shù)據(jù),可以認(rèn)為是一個(gè)個(gè)的采樣點(diǎn),Android默認(rèn)支持的采樣精度是16bit的,格式為signedPCM,所以每個(gè)采樣點(diǎn)用有符號(hào)的16位數(shù)int16_t表示。如果直接加16bit的數(shù)據(jù),肯定會(huì)造成16bit的值溢出,Android的做法是強(qiáng)轉(zhuǎn)成int32_t,相加,并把和賦值給了32bit的數(shù)。注意,相加前乘上了音量,而表達(dá)音量的數(shù)據(jù)類型也是int32_t。這樣,就能保證在這個(gè)過(guò)程中是不會(huì)溢出的。 voidAudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp __unused, int32_t* aux){ int32_t vl =t->prevVolume[0]; nt32_t vr =t->prevVolume[1]; const int16_t*in = static_cast<const int16_t *>(t->in); *out++ += (vl>> 16) * (int32_t) *in++; *out++ += (vr>> 16) * (int32_t) *in++; } 此時(shí),混音后的數(shù)據(jù)已經(jīng)存在out指向的buffer里了,然后再調(diào)用 convertMixerFormat(out, t1.mMixerFormat,outTemp, t1.mMixerInFormat, BLOCKSIZE * t1.mMixerChannelCount); 其中有函數(shù)ditherAndClamp,這個(gè)是把int32_t格式的源數(shù)據(jù)sums消減成int16_t,并把左右聲道一起放入int32_t格式的out中。 void ditherAndClamp(int32_t* out, constint32_t *sums, size_t c) { size_t i; for (i=0 ; i<c ; i++) { int32_t l = *sums++; int32_t r = *sums++; int32_t nl = l >> 12; int32_t nr = r >> 12; l = clamp16(nl); r = clamp16(nr); *out++ = (r<<16) | (l & 0xFFFF); } } 看它的做法,一個(gè)聲道的32bit的輸入,先右移12位,也就是保留前20位,然后clamp16(clamp是“夾”的意思)成16位,此時(shí)左右聲道都是16位的了。然后再把右聲道放高位,左聲道放低位這么組成一個(gè)32bit的數(shù)。 下面看看clamp16到底做了什么: static inline int16_t clamp16(int32_tsample) { if ((sample>>15) ^ (sample>>31)) sample = 0x7FFF ^ (sample>>31); return sample; } 這個(gè)函數(shù)僅僅是把溢出部分粗暴的去掉了。下面的測(cè)試程序可以很直觀的看出來(lái): int test() { for(int i=32766; i<=32776; i++){ int temp = clamp16(i); cout << "clamp16 tempInt = " << temp <<endl; } return 0; } 輸出是: 我們知道,16位的有符號(hào)數(shù)的上界是0x7FFF,也就是32767。通過(guò)測(cè)試結(jié)果發(fā)現(xiàn),小于它的數(shù)得到了保留,如32766;而大于它的數(shù)都被夾(clamp)到了32767。 那么,為什么Android要這么做呢?為什么不去優(yōu)雅的保留信號(hào)的波形,而是選擇讓它直接消減掉呢(盡管這樣勢(shì)必會(huì)造成聽(tīng)感上的Distortion)? 可能就是因?yàn)?/p> 1. 混音的情況比較少見(jiàn) 2. 混音后溢出的情況也比較少見(jiàn) 3. 如果努力去保留信號(hào)的波形,勢(shì)必會(huì)造成上一節(jié)提出的問(wèn)題 |
|
來(lái)自: mediatv > 《ffmpeg編解碼開(kāi)發(fā)》