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

可以實(shí)現(xiàn)的聲音種類:蘿莉、大叔、肥仔、搞怪、熊孩子、慢吞吞、網(wǎng)紅女、困獸、重機(jī)械、感冒、空靈等。

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

這里從maven上找了一個(gè)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增加一個(gè)頭部信息
      //TarsosDSP中也有輸出Wav格式音頻的處理器,這里沒有使用。
      byte[] wavHeader = pcm2wav(bytes);
      OutputStream wavOutPut = new FileOutputStream(tempFile);
      wavOutPut.write(wavHeader);
      wavOutPut.write(bytes);
      wavOutPut.flush();
      wavOutPut.close();

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

/**
     * 變聲
     * @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)換器。 使用插值更改采樣率, 與時(shí)間拉伸器一起可用于音高轉(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));

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

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

        /** 振幅LFO -- 聲音波動(dòng) **/
//        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)識(shí)符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)換器。 使用插值更改采樣率, 與時(shí)間拉伸器一起可用于音高轉(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)識(shí)符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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容