日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

數(shù)字音頻Mixer算法

 mediatv 2016-02-15
關(guān)閉
關(guān)閉

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)題

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多