1. 程式人生 > >Android視訊編輯器(五)音訊編解碼、從視訊中分離音訊、音訊混音、音訊音量調節等

Android視訊編輯器(五)音訊編解碼、從視訊中分離音訊、音訊混音、音訊音量調節等

    /**
     * 歸一化混音
     * */
    public static byte[] normalizationMix(byte[][] allAudioBytes){
        if (allAudioBytes == null || allAudioBytes.length == 0)
            return null;

        byte[] realMixAudio = allAudioBytes[0];

        //如果只有一個音訊的話,就返回這個音訊資料
        if(allAudioBytes.length == 1)
            return realMixAudio;

        //row 有幾個音訊要混音
        int row = realMixAudio.length /2;
        //
        short[][] sourecs = new short[allAudioBytes.length][row];
        for (int r = 0; r < 2; ++r) {
            for (int c = 0; c < row; ++c) {
                sourecs[r][c] = (short) ((allAudioBytes[r][c * 2] & 0xff) | (allAudioBytes[r][c * 2 + 1] & 0xff) << 8);
            }
        }

        //coloum第一個音訊長度 / 2
        short[] result = new short[row];
        //轉成short再計算的原因是,提供精確度,高階的混音軟體據說都是這樣做的,可以測試一下不轉short直接計算的混音結果
        for (int i = 0; i < row; i++) {
            int a = sourecs[0][i] ;
            int b = sourecs[1][i] ;
            if (a <0 && b<0){
                int i1 = a  + b  - a  * b / (-32768);
                if (i1 > 32767){
                    result[i] = 32767;
                }else if (i1 < - 32768){
                    result[i] = -32768;
                }else {
                    result[i] = (short) i1;
                }
            }else if (a > 0 && b> 0){
                int i1 = a + b - a  * b  / 32767;
                if (i1 > 32767){
                    result[i] = 32767;
                }else if (i1 < - 32768){
                    result[i] = -32768;
                }else {
                    result[i] = (short) i1;
                }
            }else {
                int i1 = a + b ;
                if (i1 > 32767){
                    result[i] = 32767;
                }else if (i1 < - 32768){
                    result[i] = -32768;
                }else {
                    result[i] = (short) i1;
                }
            }
        }
        return toByteArray(result);
    }
    public static byte[] toByteArray(short[] src) {
        int count = src.length;
        byte[] dest = new byte[count << 1];
        for (int i = 0; i < count; i++) {
            dest[i * 2 +1] = (byte) ((src[i] & 0xFF00) >> 8);
            dest[i * 2] = (byte) ((src[i] & 0x00FF));
        }
        return dest;
    }
    上面程式碼,就是一個歸一化混音演算法的java實現,而我們在過程中將原始是byte資料轉換成了short資料的原因就是為了提高精度,從而讓混音效果更好。核心原理就是上面的公式。      當然因為音訊的原始資料其實是非常多的,為了提升效率,最好使用jni實現混音相關演算法,這樣就可以實現一個效果較好的混音演算法了。專案裡面已新增相關實現,可以進行測試查閱。      但是混音時,有一個問題需要注意一下,就是音訊是存在單聲道和雙聲道,立體聲的區別的。我們讀取音訊的資訊的時候,可以看到他們是哪種聲道的,單聲道(mono),雙聲道(stereo),其實stereo應該叫立體聲,但是我查閱資料得到的資訊是,大部分的android手機其實是不支援立體聲錄音的,android平臺的很多立體聲其實只是單純的雙聲道,因為這涉及到非常底層的知識了,我也不太瞭解這一點,有知道的朋友,還望不惜賜教。這裡在進行混音的時候,不同的聲道會出現問題,因為不同的聲道資料同樣的時間戳,播放的資料量是不同的,但是我們這裡混音是按照資料量來混的,所以一個單聲道和一個多聲道的音訊直接混音的話,就會出現混音失敗。那麼如何解決這個問題呢?      其實我們可以通過將mono轉成stereo的方法來解決這個問題,居然的實現很簡單,如下程式碼
  for (int i = 0; i < monoBytes.length; i += 2) {
            stereoBytes[i*2+0] = monoBytes[i];
            stereoBytes[i*2+1] = monoBytes[i+1];
            stereoBytes[i*2+2] = monoBytes[i];
            stereoBytes[i*2+3] = monoBytes[i+1];
  }
    

音訊的音量調節

     在上面,我們實現了音訊的混音,但是如果我們想要調節音訊原始音量的大小(不是通過手機音量鍵調節),我們應該怎麼做呢,比如如果要讓你實現給視訊增加bgm的時候,你bgm的音量不能太大以至於原視訊聲音聽不見了。其實所謂音量調節,還是很簡單,就是原始byte資料 乘上一定範圍內的數值,即可實現該功能。