JAVA 實(shí)現(xiàn)變聲器(全)

可以實(shí)現(xiàn)的聲音種類:蘿莉看疙、大叔、肥仔直奋、搞怪能庆、熊孩子、慢吞吞脚线、網(wǎng)紅女搁胆、困獸、重機(jī)械邮绿、感冒渠旁、空靈等。

本方法是通過github開源的項(xiàng)目 TarsosDSP
廢話不多說斯碌,先上代碼

這里從maven上找了一個fork TarsosDSP打包的jar包一死,源碼一樣。也可以直接從TarsosDSP項(xiàng)目上把jar包下載到本地

        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-core</artifactId>
            <version>2.4.6</version>
        </dependency>
public static void main(String[] args) throws Exception {
      //這里返回的是pcm格式的音頻
      byte[] pcmBytes = speechPitchShiftMp3("/home/li/Music/silk2pcm/file.mp3", 0.6, 0.6);
      
      //如果需要轉(zhuǎn)成wav則需要給pcmBytes增加一個頭部信息
      //TarsosDSP中也有輸出Wav格式音頻的處理器傻唾,這里沒有使用投慈。
      byte[] wavHeader = pcm2wav(bytes);
      OutputStream wavOutPut = new FileOutputStream(tempFile);
      wavOutPut.write(wavHeader);
      wavOutPut.write(bytes);
      wavOutPut.flush();
      wavOutPut.close();

      // 對于各種聲音類型承耿,以及所需添加的處理器,還有處理器參數(shù)代碼伪煤,將在本文最后給出加袋。
      //如果需要轉(zhuǎn)mp3格式的,也可以給我留言抱既,我會加上职烧。
}

/**
     * 變聲
     * @param speedFactor 變速率 (0,2) 大于1為加快語速,小于1為放慢語速
     * @param rateFactor 音調(diào)變化率 (0,2) 大于1為降低音調(diào)(深沉)防泵,小于1為提升音調(diào)(尖銳)
     * @return 變聲后的MP3數(shù)據(jù)輸入流
     */
    public static byte[] speechPitchShiftMp3(String fileUrl, double rateFactor, double speedFactor) throws IOException, UnsupportedAudioFileException {

        WaveformSimilarityBasedOverlapAdd w = new WaveformSimilarityBasedOverlapAdd(WaveformSimilarityBasedOverlapAdd.Parameters.speechDefaults(rateFactor, 16000));
        int inputBufferSize = w.getInputBufferSize();
        int overlap = w.getOverlap();

        AudioDispatcher dispatcher = AudioDispatcherFactory.fromPipe(fileUrl,16000,inputBufferSize,overlap);
        w.setDispatcher(dispatcher);
        dispatcher.addAudioProcessor(w);

        /** 采樣率轉(zhuǎn)換器蚀之。 使用插值更改采樣率, 與時間拉伸器一起可用于音高轉(zhuǎn)換。 **/
        dispatcher.addAudioProcessor(new RateTransposer(speedFactor));
        AudioOutputToByteArray out = new AudioOutputToByteArray();


        /** 聲音速率轉(zhuǎn)換器 -- 失敗 **/
        /*SoundTouchRateTransposer soundTouchRateTransposer = new SoundTouchRateTransposer(2);
        soundTouchRateTransposer.setDispatcher(dispatcher);
        dispatcher.addAudioProcessor(soundTouchRateTransposer);*/

        /** 正弦波發(fā)生器 -- 無反應(yīng) **/
        /*SineGenerator sineGenerator = new SineGenerator(0.5, 0.5);
        dispatcher.addAudioProcessor(sineGenerator);*/

        /** 音調(diào)轉(zhuǎn)換器 -- 無效果 **/
//        dispatcher.addAudioProcessor(new PitchShifter(0.1,16000,448,overlap));

        /** 制粒機(jī)使用顆粒合成回放樣本捷泞。方法可用于控制播放速率足删,音高,顆粒大小锁右, -- 無效果 **/
//        dispatcher.addAudioProcessor(new OptimizedGranulator(16000, 448));

        /** 噪音產(chǎn)生器 -- 有效果 **/
//        dispatcher.addAudioProcessor(new NoiseGenerator(0.2   ));

        /** 增益處理器  增益為1失受,則無任何反應(yīng)。 增益大于1表示音量增加a -- 有反應(yīng) **/
//        dispatcher.addAudioProcessor(new GainProcessor(10));

        /**鑲邊效果 -- 有反應(yīng) **/
//        dispatcher.addAudioProcessor(new FlangerEffect(64, 0.3, 16000, 16000));// 回聲效果
//        dispatcher.addAudioProcessor(new FlangerEffect(1 << 4, 0.8, 8000, 2000));// 感冒
//        dispatcher.addAudioProcessor(new ZeroCrossingRateProcessor());//感冒

        /** 淡出 --聲音慢慢變小 **/
//        dispatcher.addAudioProcessor(new FadeOut(5));

        /** 淡入-- 聲音慢慢變大 **/
//        dispatcher.addAudioProcessor(new FadeIn(5));

        /** 在信號上添加回聲效果咏瑟。echoLength以秒為單位  elay回聲的衰減拂到,介于0到1之間的值。1表示無衰減码泞,0表示立即衰減 **/
        dispatcher.addAudioProcessor(new DelayEffect(0.2, 0.24, 12000) );

        /** 調(diào)幅噪聲 -- 將聲音轉(zhuǎn)換為噪聲**/
//        dispatcher.addAudioProcessor(new AmplitudeModulatedNoise());

        /** 振幅LFO -- 聲音波動 **/
//        dispatcher.addAudioProcessor(new AmplitudeLFO());

        dispatcher.addAudioProcessor(out);

        dispatcher.run();



//        return new ByteArrayInputStream(out.getData());
        return out.getData();
    }


    public static byte[] pcm2wav(byte[] bytes) throws IOException {
        //填入?yún)?shù)兄旬,比特率等等。這里用的是16位單聲道 8000 hz
        WaveHeader header = new WaveHeader();

        //長度字段 = 內(nèi)容的大杏嗔取(PCMSize) + 頭部字段的大小(不包括前面4字節(jié)的標(biāo)識符RIFF以及fileLength本身的4字節(jié))
        header.fileLength = bytes.length + (44 - 8);
        header.FmtHdrLeth = 16;
        header.BitsPerSample = 16;
        header.Channels = 1;
        header.FormatTag = 0x0001;
        header.SamplesPerSec = 16000;
        header.BlockAlign = (short)(header.Channels * header.BitsPerSample / 8);
        header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
        header.DataHdrLeth = bytes.length;

        byte[] h = header.getHeader();
        assert h.length == 44; //WAV標(biāo)準(zhǔn)辖试,頭部應(yīng)該是44字節(jié)
        return h;
    }


AudioOutputToByteArray代碼


import be.tarsos.dsp.AudioEvent;
import be.tarsos.dsp.AudioProcessor;
import org.tritonus.share.sampled.file.AudioOutputStream;

import java.io.ByteArrayOutputStream;

public class AudioOutputToByteArray implements AudioProcessor {
    private boolean isDone = false;
    private byte[] out = null;
    private ByteArrayOutputStream bos;
    private AudioOutputStream outputStream;

    public AudioOutputToByteArray() {
        bos = new ByteArrayOutputStream();
    }

    public ByteArrayOutputStream getBos() {
        return bos;
    }

    public byte[] getData() {
        while (!isDone && out == null) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException ignored) {}
        }

        return out;
    }

    @Override
    public boolean process(AudioEvent audioEvent) {

        bos.write(audioEvent.getByteBuffer(),0,audioEvent.getByteBuffer().length);
        return true;
    }

    @Override
    public void processingFinished() {
        out = bos.toByteArray().clone();
        bos = null;
        isDone = true;
    }
}

WaveHeader代碼


import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class WaveHeader {
    public final char fileID[] = {'R', 'I', 'F', 'F'};
    public int fileLength;
    public char wavTag[] = {'W', 'A', 'V', 'E'};;
    public char FmtHdrID[] = {'f', 'm', 't', ' '};
    public int FmtHdrLeth;
    public short FormatTag;
    public short Channels;
    public int SamplesPerSec;
    public int AvgBytesPerSec;
    public short BlockAlign;
    public short BitsPerSample;
    public char DataHdrID[] = {'d','a','t','a'};
    public int DataHdrLeth;

    public byte[] getHeader() throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        WriteChar(bos, fileID);
        WriteInt(bos, fileLength);
        WriteChar(bos, wavTag);
        WriteChar(bos, FmtHdrID);
        WriteInt(bos,FmtHdrLeth);
        WriteShort(bos,FormatTag);
        WriteShort(bos,Channels);
        WriteInt(bos,SamplesPerSec);
        WriteInt(bos,AvgBytesPerSec);
        WriteShort(bos,BlockAlign);
        WriteShort(bos,BitsPerSample);
        WriteChar(bos,DataHdrID);
        WriteInt(bos,DataHdrLeth);
        bos.flush();
        byte[] r = bos.toByteArray();
        bos.close();
        return r;
    }

    private void WriteShort(ByteArrayOutputStream bos, int s) throws IOException {
        byte[] mybyte = new byte[2];
        mybyte[1] =(byte)( (s << 16) >> 24 );
        mybyte[0] =(byte)( (s << 24) >> 24 );
        bos.write(mybyte);
    }


    private void WriteInt(ByteArrayOutputStream bos, int n) throws IOException {
        byte[] buf = new byte[4];
        buf[3] =(byte)( n >> 24 );
        buf[2] =(byte)( (n << 8) >> 24 );
        buf[1] =(byte)( (n << 16) >> 24 );
        buf[0] =(byte)( (n << 24) >> 24 );
        bos.write(buf);
    }

    private void WriteChar(ByteArrayOutputStream bos, char[] id) {
        for (int i=0; i<id.length; i++) {
            char c = id[i];
            bos.write(c);
        }
    }

各種變聲器參數(shù)


import be.tarsos.dsp.AudioDispatcher;
import be.tarsos.dsp.WaveformSimilarityBasedOverlapAdd;
import be.tarsos.dsp.ZeroCrossingRateProcessor;
import be.tarsos.dsp.effects.DelayEffect;
import be.tarsos.dsp.io.jvm.AudioDispatcherFactory;
import be.tarsos.dsp.resample.RateTransposer;
import com.bleege.recordingsound.utils.AudioOutputToByteArray;
import com.bleege.recordingsound.utils.WaveHeader;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Consumer;

@Slf4j
public enum SoundEnum {
    LUOLI(0.6, 0.6, "蘿莉", 1, dispatcher -> {}),
    DASHU(1.2, 1.2, "大叔", 2, dispatcher -> {}),
    FEIZAI(1.5, 1.5, "肥仔", 3, dispatcher -> {}),
    GAOGUAI(1.5, 0.8, "搞怪", 4, dispatcher -> {}),
    XIONGHAIZI(0.73, 0.73, "熊孩子", 5, dispatcher -> {}),
    MANTUNTUN(0.35,1, "慢吞吞",6 , dispatcher -> {}),
    WANGHONGNV(1.2,0.7, "網(wǎng)紅女",7 , dispatcher -> {}),

    /**
     * dispatcher.addAudioProcessor(new DelayEffect(0.2, 0.24, 12000) );
     */
    KUNSHOU(1.55,1.55, "困獸", 8, dispatcher -> dispatcher.addAudioProcessor(new DelayEffect(0.2, 0.24, 12000))),

    /**
     * dispatcher.addAudioProcessor(new DelayEffect(0.2, 0.24, 12000) );
     */
    ZHONGJIXIE(1.50,1.50, "重機(jī)械", 9, dispatcher -> dispatcher.addAudioProcessor(new DelayEffect(0.2, 0.24, 12000))),

    /**
     *          dispatcher.addAudioProcessor(new FlangerEffect(1 << 4, 0.8, 8000, 2000));
     *         dispatcher.addAudioProcessor(new ZeroCrossingRateProcessor());
     */
    GANMAO(1.05,1.05, "感冒", 10, dispatcher -> {
        dispatcher.addAudioProcessor(new DelayEffect(0.2, 0.24, 12000));
        dispatcher.addAudioProcessor(new ZeroCrossingRateProcessor());
    }),

    /**
     *          dispatcher.addAudioProcessor(new DelayEffect(0.8, 0.5, 12000) );
     *         dispatcher.addAudioProcessor(new DelayEffect(0.5, 0.3, 8000) );
     */
    KONGLING(1, 1, "空靈", 11, dispatcher -> {
        dispatcher.addAudioProcessor(new DelayEffect(0.8, 0.5, 12000) );
        dispatcher.addAudioProcessor(new DelayEffect(0.5, 0.3, 8000) );
    });

    /**
     * @param speedFactor 變速率 (0,2) 大于1為加快語速,小于1為放慢語速
     * @param rateFactor 音調(diào)變化率 (0,2) 大于1為降低音調(diào)(深沉)劈狐,小于1為提升音調(diào)(尖銳)
     */
    SoundEnum(double rateFactor, double speedFactor, String name, int type, Consumer<AudioDispatcher> consumer){
        this.rateFactor = rateFactor;
        this.speedFactor = speedFactor;
        this.name = name;
        this.type = type;
        this.consumer = consumer;
    }
    private double rateFactor;
    private double speedFactor;
    private String name;
    private int type;
    private Consumer consumer;


    public byte[] run(String fileUrl){
        WaveformSimilarityBasedOverlapAdd w = new WaveformSimilarityBasedOverlapAdd(WaveformSimilarityBasedOverlapAdd.Parameters.speechDefaults(rateFactor, 16000));
        int inputBufferSize = w.getInputBufferSize();
        int overlap = w.getOverlap();

        AudioDispatcher dispatcher = AudioDispatcherFactory.fromPipe(fileUrl,16000,inputBufferSize,overlap);
        w.setDispatcher(dispatcher);
        dispatcher.addAudioProcessor(w);

        /** 采樣率轉(zhuǎn)換器。 使用插值更改采樣率, 與時間拉伸器一起可用于音高轉(zhuǎn)換呐馆。 **/
        dispatcher.addAudioProcessor(new RateTransposer(speedFactor));
        AudioOutputToByteArray out = new AudioOutputToByteArray();



        consumer.accept(dispatcher);

        dispatcher.addAudioProcessor(out);
        dispatcher.run();


        return out.getData();
    }


    public static byte[] pcm2wav(byte[] bytes) {
        try {
            //填入?yún)?shù)肥缔,比特率等等。這里用的是16位單聲道 8000 hz
            WaveHeader header = new WaveHeader();

            //長度字段 = 內(nèi)容的大行诶础(PCMSize) + 頭部字段的大小(不包括前面4字節(jié)的標(biāo)識符RIFF以及fileLength本身的4字節(jié))
            header.fileLength = bytes.length + (44 - 8);
            header.FmtHdrLeth = 16;
            header.BitsPerSample = 16;
            header.Channels = 1;
            header.FormatTag = 0x0001;
            header.SamplesPerSec = 16000;
            header.BlockAlign = (short)(header.Channels * header.BitsPerSample / 8);
            header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
            header.DataHdrLeth = bytes.length;

            byte[] h = header.getHeader();
            assert h.length == 44; //WAV標(biāo)準(zhǔn)续膳,頭部應(yīng)該是44字節(jié)
            return h;
        } catch (IOException e) {
            log.error("pcm2wav-error", e);
        }
        return null;
    }


    public static Optional<SoundEnum> getInstance(int type){
        for (int i = 0; i < SoundEnum.values().length; i++) {
            if(SoundEnum.values()[i].type == type)
                return Optional.of(SoundEnum.values()[i]);
        }
        return Optional.empty();
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市收班,隨后出現(xiàn)的幾起案子坟岔,更是在濱河造成了極大的恐慌,老刑警劉巖摔桦,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件社付,死亡現(xiàn)場離奇詭異承疲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸥咖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門燕鸽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人啼辣,你說我怎么就攤上這事啊研。” “怎么了鸥拧?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵党远,是天一觀的道長。 經(jīng)常有香客問我富弦,道長沟娱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任舆声,我火速辦了婚禮花沉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘媳握。我一直安慰自己碱屁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布蛾找。 她就那樣靜靜地躺著娩脾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪打毛。 梳的紋絲不亂的頭發(fā)上柿赊,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機(jī)與錄音幻枉,去河邊找鬼碰声。 笑死,一個胖子當(dāng)著我的面吹牛熬甫,可吹牛的內(nèi)容都是我干的胰挑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼椿肩,長吁一口氣:“原來是場噩夢啊……” “哼瞻颂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起郑象,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤贡这,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后厂榛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盖矫,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丽惭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了炼彪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吐根。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖辐马,靈堂內(nèi)的尸體忽然破棺而出拷橘,到底是詐尸還是另有隱情,我是刑警寧澤喜爷,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布冗疮,位于F島的核電站,受9級特大地震影響檩帐,放射性物質(zhì)發(fā)生泄漏术幔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一湃密、第九天 我趴在偏房一處隱蔽的房頂上張望诅挑。 院中可真熱鬧,春花似錦泛源、人聲如沸拔妥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽没龙。三九已至,卻和暖如春缎玫,著一層夾襖步出監(jiān)牢的瞬間硬纤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工赃磨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筝家,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓邻辉,卻偏偏與公主長得像肛鹏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子恩沛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 逅弈 轉(zhuǎn)載請注明原創(chuàng)出處,謝謝缕减! 以前的日子 以前我們寫代碼時雷客,jar包都默認(rèn)放在一個叫 /lib 的目錄下,然后...
    逅弈閱讀 2,885評論 3 45
  • 第1章 Maven 介紹 什么是 Maven 什么是 Maven Maven 的正確發(fā)音是[?mev?n]桥狡,而不是...
    強(qiáng)某某閱讀 2,380評論 0 25
  • 一搅裙、Maven坐標(biāo)和倉庫 ??Maven坐標(biāo)是一個唯一的標(biāo)識皱卓,用于表示我們的項(xiàng)目的一些信息或者引用jar包的位置。...
    嗷老板閱讀 807評論 0 5
  • 前言 在Java項(xiàng)目開發(fā)中部逮,項(xiàng)目的編譯娜汁、測試、打包等是比較繁瑣的兄朋,屬于重復(fù)勞動的工作掐禁,浪費(fèi)人力和時間成本。以往開發(fā)...
    JourWon閱讀 1,122評論 0 1
  • 2018.12.11 起床:5:32 就寢:10:30 天氣:陰 心情:一般 紀(jì)念日:無 任務(wù)清單 昨日完成的任務(wù)...
    長安十二閱讀 138評論 0 0