JAVA 实现变声器(全)

可以实现的声音种类:萝莉、大叔、肥仔、搞怪、熊孩子、慢吞吞、网红女、困兽、重机械、感冒、空灵等。

本方法是通过github开源的项目 TarsosDSP
废话不多说,先上代码

这里从maven上找了一个fork TarsosDSP打包的jar包,源码一样。也可以直接从TarsosDSP项目上把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);
      
      //如果需要转成wav则需要给pcmBytes增加一个头部信息
      //TarsosDSP中也有输出Wav格式音频的处理器,这里没有使用。
      byte[] wavHeader = pcm2wav(bytes);
      OutputStream wavOutPut = new FileOutputStream(tempFile);
      wavOutPut.write(wavHeader);
      wavOutPut.write(bytes);
      wavOutPut.flush();
      wavOutPut.close();

      // 对于各种声音类型,以及所需添加的处理器,还有处理器参数代码,将在本文最后给出。
      //如果需要转mp3格式的,也可以给我留言,我会加上。
}

/**
     * 变声
     * @param speedFactor 变速率 (0,2) 大于1为加快语速,小于1为放慢语速
     * @param rateFactor 音调变化率 (0,2) 大于1为降低音调(深沉),小于1为提升音调(尖锐)
     * @return 变声后的MP3数据输入流
     */
    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);

        /** 采样率转换器。 使用插值更改采样率, 与时间拉伸器一起可用于音高转换。 **/
        dispatcher.addAudioProcessor(new RateTransposer(speedFactor));
        AudioOutputToByteArray out = new AudioOutputToByteArray();


        /** 声音速率转换器 -- 失败 **/
        /*SoundTouchRateTransposer soundTouchRateTransposer = new SoundTouchRateTransposer(2);
        soundTouchRateTransposer.setDispatcher(dispatcher);
        dispatcher.addAudioProcessor(soundTouchRateTransposer);*/

        /** 正弦波发生器 -- 无反应 **/
        /*SineGenerator sineGenerator = new SineGenerator(0.5, 0.5);
        dispatcher.addAudioProcessor(sineGenerator);*/

        /** 音调转换器 -- 无效果 **/
//        dispatcher.addAudioProcessor(new PitchShifter(0.1,16000,448,overlap));

        /** 制粒机使用颗粒合成回放样本。方法可用于控制播放速率,音高,颗粒大小, -- 无效果 **/
//        dispatcher.addAudioProcessor(new OptimizedGranulator(16000, 448));

        /** 噪音产生器 -- 有效果 **/
//        dispatcher.addAudioProcessor(new NoiseGenerator(0.2   ));

        /** 增益处理器  增益为1,则无任何反应。 增益大于1表示音量增加a -- 有反应 **/
//        dispatcher.addAudioProcessor(new GainProcessor(10));

        /**镶边效果 -- 有反应 **/
//        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) );

        /** 调幅噪声 -- 将声音转换为噪声**/
//        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 {
        //填入参数,比特率等等。这里用的是16位单声道 8000 hz
        WaveHeader header = new WaveHeader();

        //长度字段 = 内容的大小(PCMSize) + 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
        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标准,头部应该是44字节
        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);
        }
    }

各种变声器参数


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, "网红女",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, "重机械", 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 音调变化率 (0,2) 大于1为降低音调(深沉),小于1为提升音调(尖锐)
     */
    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);

        /** 采样率转换器。 使用插值更改采样率, 与时间拉伸器一起可用于音高转换。 **/
        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 {
            //填入参数,比特率等等。这里用的是16位单声道 8000 hz
            WaveHeader header = new WaveHeader();

            //长度字段 = 内容的大小(PCMSize) + 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
            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标准,头部应该是44字节
            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();
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 逅弈 转载请注明原创出处,谢谢! 以前的日子 以前我们写代码时,jar包都默认放在一个叫 /lib 的目录下,然后...
    逅弈阅读 2,866评论 3 45
  • 第1章 Maven 介绍 什么是 Maven 什么是 Maven Maven 的正确发音是[ˈmevən],而不是...
    强某某阅读 2,366评论 0 25
  • 一、Maven坐标和仓库   Maven坐标是一个唯一的标识,用于表示我们的项目的一些信息或者引用jar包的位置。...
    嗷老板阅读 792评论 0 5
  • 前言 在Java项目开发中,项目的编译、测试、打包等是比较繁琐的,属于重复劳动的工作,浪费人力和时间成本。以往开发...
    JourWon阅读 1,114评论 0 1
  • 2018.12.11 起床:5:32 就寝:10:30 天气:阴 心情:一般 纪念日:无 任务清单 昨日完成的任务...
    长安十二阅读 135评论 0 0