编写java变声器需要做的前期准备

安装 ffmgeg 下载地址 Releases · BtbN/FFmpeg-Builds · GitHub

win系统下载 ffmpeg-N-103272-g7bba0dd638-win64-gpl.zip

配置环境变量到 bin目录 

fmod java 变声 java 变声器_开发语言

 新建maven程序加入两个引用

<!-- https://mvnrepository.com/artifact/com.github.st-h/TarsosDSP -->
		<dependency>
			<groupId>com.github.st-h</groupId>
			<artifactId>TarsosDSP</artifactId>
			<version>2.4.1</version>
		</dependency>
		<dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-core</artifactId>
            <version>2.4.6</version>
        </dependency>

新建两个基础类

AudioOutputToByteArray

import be.tarsos.dsp.AudioEvent;
import be.tarsos.dsp.AudioProcessor;

import java.io.ByteArrayOutputStream;

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

    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);
        }
    }
}

现在新建执行类和方法,先在d盘放一个1.mp3的文件。以下代码就是把1.mp3转换成变声后的2.mp3

byte[] pcmBytes = speechPitchShiftMp3("d://1.mp3", 0.73, 0.73); 后面的0.73就是变声参数。最后会给出各种变声参数

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.sound.sampled.UnsupportedAudioFileException;

import be.tarsos.dsp.AudioDispatcher;
import be.tarsos.dsp.WaveformSimilarityBasedOverlapAdd;
import be.tarsos.dsp.effects.DelayEffect;
import be.tarsos.dsp.io.jvm.AudioDispatcherFactory;
import be.tarsos.dsp.resample.RateTransposer;

public class ceshi {
	public static void main(String[] args) throws Exception {
	      //这里返回的是pcm格式的音频
	      byte[] pcmBytes = speechPitchShiftMp3("d://1.mp3", 0.73, 0.73);
	      
	      //如果需要转成wav则需要给pcmBytes增加一个头部信息
	      //TarsosDSP中也有输出Wav格式音频的处理器,这里没有使用。
	      byte[] wavHeader = pcm2wav(pcmBytes);
	      OutputStream wavOutPut = new FileOutputStream("d://2.mp3");
	      wavOutPut.write(wavHeader);
	      wavOutPut.write(pcmBytes);
	      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;
	    }


}

 各种参数类

LUOLI(0.6, 0.6,  1, dispatcher -> {})
byte[] pcmBytes = speechPitchShiftMp3("d://1.mp3", 0.73, 0.73);改为
byte[] pcmBytes = speechPitchShiftMp3("d://1.mp3", 0.6, 0.6);就可以了
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 java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Consumer;

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();
    }
}