第三方扩展haowen

JAVA 实现变声器(全)

2019-09-25  本文已影响0人  笨笨翔

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

本方法是通过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();
    }
}

上一篇下一篇

猜你喜欢

热点阅读