溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android中怎么實現(xiàn)混音

發(fā)布時間:2021-06-28 11:48:15 來源:億速云 閱讀:401 作者:小新 欄目:移動開發(fā)

小編給大家分享一下Android中怎么實現(xiàn)混音,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

初識音頻

從初中物理上我們就學到,聲音是一種波。計算機只能處理離散的信號,通過收集足夠多的離散的信號,來不斷逼近波形,這個過程我們叫做采樣。怎么樣才能更好的還原聲音信息呢?這里很自然引出兩個概念了。

Android中怎么實現(xiàn)混音

采樣頻率(Sample Rate):每秒采集聲音的數(shù)量,它用赫茲(Hz)來表示。

采樣率越高越靠近原聲音的波形,常見的采樣率有以下幾種:

  • 8khz:電話等使用,對于記錄人聲已經足夠使用。

  • 22.05khz:廣播使用頻率。

  • 44.1kb:音頻CD。

  • 48khz:DVD、數(shù)字電視中使用。

  • 96khz-192khz:DVD-Audio、藍光高清等使用。

采樣精度(Bit Depth): 它表示每次采樣的精度,位數(shù)越多,能記錄的范圍就越大。

采樣精度常用范圍為8bit-32bit,而CD中一般都使用16bit。

把聲音記錄下來之后,通過喇叭的震動把波再還給空氣傳到你的耳朵就完成了這個完美的循環(huán)了。但是富有創(chuàng)造力的人類不會限制于此就結束了,很快人們發(fā)現(xiàn),當把不同的聲音傳遞到不同的喇叭的時候,竟然會驚奇地讓聲音變得有空間感了,即時是同一個聲音,也比單個通道能獲得更好的體驗,于是就出現(xiàn)了什么立體聲,5.1 環(huán)繞等看起來很高大上的東西。所以,音頻又多了一個東西:

聲音通道(Channel): 你知道每個通道存儲的聲音會從其中的一個喇叭出來就好了,不過可以通過算法的模擬來讓沒有那么多喇叭也能出來類似的效果。

有了聲音通道,樂隊在錄音的時候就可以每個人插一條音軌了,然后每一個聲音可以寫到不同的通道里面,當然,實際錄音當然都是后期混音而成的。下面介紹的其中一個混音算法會用到聲音通道這個特性。

最后再介紹一個大家經??吹降母拍睿?/p>

比特率(bps [bits per second]): 其實看單位就很容易知道它要表達的意思了,就是每秒鐘要播放多少 bit 的數(shù)據(jù)。公式一目了然:

比特率 = 采樣率 × 采樣深度 × 通道。

比如 采樣率 = 44100,采樣深度 = 16,通道 = 2 的音頻的的比特率就是 44100 16 2 = 1411200 bps。

一般來說,比特率越高,音頻質量越好。要注意一些比特率的換算不是 1024 作為一個級別換算的哈。

1,000 bps = 【1 kbps】 = 1,000 bit/s
1,000,000 bps = 【1 Mbps】 = 1,000,000 bit/s 
1,000,000,000 bps = 【1 Gbps】 = 1,000,000,000 bit/s

音頻在計算機中的表示

我們來看一下真實音頻在計算機中究竟是怎樣的表示狀態(tài),這里指的是原始的數(shù)據(jù)表示,而非編碼(Mp3,Acc等)后的表示,平時我們看到的.wav后綴的音頻,把前面 44 個字節(jié)用于記錄采樣率、通道等的頭部信息去掉后就是就是原始的音頻數(shù)據(jù)了。

Android中怎么實現(xiàn)混音

在理解了上面的概念之后,我們再來看這張圖。對于文件頭部信息我們就不詳細介紹了,不影響我們理解介紹的混音處理方式,需要了解的可以點擊這里。

我們抽取其中的一個采樣來看,這里我加多了一個通道,便于大家理解通道的存儲位置。

Android中怎么實現(xiàn)混音

不難理解,這個采樣中有三個通道,每通道采樣精度是 16 比特。每個采樣值的排序是 Little-Endian 低位在前的方式,比如通道 1 的采樣值就是 AB03, 每個采樣值的大小表示的是幅度信息。

混音的原理

音頻混音的原理: 空氣中聲波的疊加等價于量化的語音信號的疊加。

Android中怎么實現(xiàn)混音

這句話可能有點拗口,我們從程序員的角度去觀察就不難理解了。下圖是兩條音軌的數(shù)據(jù),將每個通道的值做線性疊加后的值就是混音的結果了。比如音軌A和音軌B的疊加,A.1 表示 A 音軌的 1 通道的值 AB03 , B.1 表示 B 音軌的 1 通道的值 1122 , 結果是 bc25,然后按照低位在前的方式排列,在合成音軌中就是 25bc,這里的表示都是 16 進制的。

Android中怎么實現(xiàn)混音

直接加起來就可以了?事情如果這么簡單就好了。音頻設備支持的采樣精度肯定都是有限的,一般為 8 位或者 16 位,大一些的為 32 位。在音軌數(shù)據(jù)疊加的過程中,肯定會導致溢出的問題。為了解決這個問題,人們找了不少的辦法。這里我主要介紹幾種我用過的,并給出相關代碼實現(xiàn)和最終的混音效果對比結果。

線性疊加平均

這種辦法的原理非常簡單粗暴,也不會引入噪音。原理就是把不同音軌的通道值疊加之后取平均值,這樣就不會有溢出的問題了。但是會帶來的后果就是某一路或幾路音量特別小那么整個混音結果的音量會被拉低。

以下的的單路音軌的音頻參數(shù)我們假定為采樣頻率一致,通道數(shù)一致,通道采樣精度統(tǒng)一為 16 位。

其中參數(shù) bMulRoadAudios 的一維表示的是音軌數(shù),二維表示該音軌的音頻數(shù)據(jù)。

Java 代碼實現(xiàn):

@Override
 public byte[] mixRawAudioBytes(byte[][] bMulRoadAudios) {

  if (bMulRoadAudios == null || bMulRoadAudios.length == 0)
  return null;
  byte[] realMixAudio = bMulRoadAudios[0];
  if(realMixAudio == null){
  return null;
  }
  final int row = bMulRoadAudios.length;

  //單路音軌
  if (bMulRoadAudios.length == 1)
  return realMixAudio;
  //不同軌道長度要一致,不夠要補齊
  for (int rw = 0; rw < bMulRoadAudios.length; ++rw) {
  if (bMulRoadAudios[rw] == null || bMulRoadAudios[rw].length != realMixAudio.length) {
   return null;
  }
  }

  /**
  * 精度為 16位
  */
  int col = realMixAudio.length / 2;
  short[][] sMulRoadAudios = new short[row][col];
  for (int r = 0; r < row; ++r) {
  for (int c = 0; c < col; ++c) {
   sMulRoadAudios[r][c] = (short) ((bMulRoadAudios[r][c * 2] & 0xff) | (bMulRoadAudios[r][c * 2 + 1] & 0xff) << 8);
  }
  }

  short[] sMixAudio = new short[col];
  int mixVal;
  int sr = 0;
  for (int sc = 0; sc < col; ++sc) {
  mixVal = 0;
  sr = 0;
  for (; sr < row; ++sr) {
   mixVal += sMulRoadAudios[sr][sc];
  }
  sMixAudio[sc] = (short) (mixVal / row);
  }

  for (sr = 0; sr < col; ++sr) {
  realMixAudio[sr * 2] = (byte) (sMixAudio[sr] & 0x00FF);
  realMixAudio[sr * 2 + 1] = (byte) ((sMixAudio[sr] & 0xFF00) >> 8);
  }
  return realMixAudio;
 }

自適應混音

參與混音的多路音頻信號自身的特點,以它們自身的比例作為權重,從而決定它們在合成后的輸出中所占的比重。具體的原理可以參考這篇論文:快速實時自適應混音方案研究。這種方法對于音軌路數(shù)比較多的情況應該會比上面的平均法要好,但是可能會引入噪音。

Java 代碼實現(xiàn):

 @Override
 public byte[] mixRawAudioBytes(byte[][] bMulRoadAudios) {
  //簡化檢查代碼
  /**
  * 精度為 16位
  */
  int col = realMixAudio.length / 2;
  short[][] sMulRoadAudios = new short[row][col];
  for (int r = 0; r < row; ++r) {
  for (int c = 0; c < col; ++c) {
   sMulRoadAudios[r][c] = (short) ((bMulRoadAudios[r][c * 2] & 0xff) | (bMulRoadAudios[r][c * 2 + 1] & 0xff) << 8);
  }
  }

  short[] sMixAudio = new short[col];
  int sr = 0;
  double wValue;
  double absSumVal;
  for (int sc = 0; sc < col; ++sc) {
  sr = 0;
  wValue = 0;
  absSumVal = 0;
  for (; sr < row; ++sr) {
   wValue += Math.pow(sMulRoadAudios[sr][sc], 2) * Math.signum(sMulRoadAudios[sr][sc]);
   absSumVal += Math.abs(sMulRoadAudios[sr][sc]);
  }
  sMixAudio[sc] = absSumVal == 0 ? 0 : (short) (wValue / absSumVal);
  }

  for (sr = 0; sr < col; ++sr) {
  realMixAudio[sr * 2] = (byte) (sMixAudio[sr] & 0x00FF);
  realMixAudio[sr * 2 + 1] = (byte) ((sMixAudio[sr] & 0xFF00) >> 8);
  }
  return realMixAudio;
 }

多通道混音

在實際開發(fā)中,我發(fā)現(xiàn)上面的兩種方法都不能達到滿意的效果。一方面是和音樂相關,對音頻質量要求比較高;另外一方面是通過手機錄音,效果肯定不會太好。不知道從哪里冒出來的靈感,為什么不試著把不同的音軌數(shù)據(jù)塞到不同的通道上,讓聲音從不同的喇叭上同時發(fā)出,這樣也可以達到混音的效果??!而且不會有音頻數(shù)據(jù)損失的問題,能很完美地呈現(xiàn)原來的聲音。

于是我開始查了一下 Android 對多通道的支持情況,對應代碼可以在android.media.AudioFormat中查看,結果如下:

public static final int CHANNEL_OUT_FRONT_LEFT = 0x4;
public static final int CHANNEL_OUT_FRONT_RIGHT = 0x8;
public static final int CHANNEL_OUT_FRONT_CENTER = 0x10;
public static final int CHANNEL_OUT_LOW_FREQUENCY = 0x20;
public static final int CHANNEL_OUT_BACK_LEFT = 0x40;
public static final int CHANNEL_OUT_BACK_RIGHT = 0x80;
public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100;
public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200;
public static final int CHANNEL_OUT_BACK_CENTER = 0x400;
public static final int CHANNEL_OUT_SIDE_LEFT =  0x800;
public static final int CHANNEL_OUT_SIDE_RIGHT = 0x1000;

一共支持 10 個通道,對于我的情況來說是完全夠用了。我們的耳機一般只有左右聲道,那些更多通道的支持是 Android 系統(tǒng)內部通過軟件算法模擬實現(xiàn)的,至于具體如何實現(xiàn)的,我也沒有深入了解,在這里我們知道這回事就行了。我們平時所熟知的立體聲,5.1 環(huán)繞等就是上面那些通道的組合。

 int CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT;
 int CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT);
 int CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
   CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);

知道原理之后,實現(xiàn)起來非常簡單,下面是具體的代碼:

  @Override
  public byte[] mixRawAudioBytes(byte[][] bMulRoadAudios) {
   int roadLen = bMulRoadAudios.length;
   //單路音軌
   if (roadLen == 1)
    return bMulRoadAudios[0];
   int maxRoadByteLen = 0;
   for(byte[] audioData : bMulRoadAudios){
    if(maxRoadByteLen < audioData.length){
     maxRoadByteLen = audioData.length;
    }
   }

   byte[] resultMixData = new byte[maxRoadByteLen * roadLen];
   for(int i = 0; i != maxRoadByteLen; i = i + 2){
    for(int r = 0; r != roadLen; r++){
     resultMixData[i * roadLen + 2 * r] = bMulRoadAudios[r][i];
     resultMixData[i * roadLen + 2 * r + 1] = bMulRoadAudios[r][i+1];
    }
   }
   return resultMixData;
  }

結果比較

線性疊加平均法雖然看起來很簡單,但是在音軌數(shù)量比較少的時候取得的效果可能會比復雜的自適應混音法要出色。
自適應混音法比較合適音軌數(shù)量比較多的情況,但是可能會引入一些噪音。

多通道混音雖然看起來很完美,但是產生的文件大小是數(shù)倍于其他的處理方法。

沒有銀彈,還是要根據(jù)自己的應用場景來選擇,多試一下。

下面是我錄的兩路音軌:

音軌一:Android中怎么實現(xiàn)混音

音軌二:Android中怎么實現(xiàn)混音

線性疊加平均法:Android中怎么實現(xiàn)混音

自適應混音法:Android中怎么實現(xiàn)混音

多通道混音:Android中怎么實現(xiàn)混音

采樣頻率、采樣精度和通道數(shù)不同的情況如何處理?

不同采樣頻率需要算法進行重新采樣處理,讓所有音軌在同一采樣率下進行混音,這個比較復雜,等有機會再寫篇文章介紹。

采樣精度不同比較好處理,向上取精度較高的作為基準即可,高位補0;如果是需要取向下精度作為基準的,那么就要把最大通道值和基準最大值取個倍數(shù),把數(shù)值都降到最大基準數(shù)以下,然后把低位移除。

通道數(shù)不同的情況也和精度不同的情況相似處理。

以上是“Android中怎么實現(xiàn)混音”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI