美文网首页
讯飞语音合成工具类

讯飞语音合成工具类

作者: kenshine666 | 来源:发表于2020-07-08 18:13 被阅读0次

今天看了看讯飞的语音合成接口,写了个工具类:

@Component
public class XunFeiUtils {
    private XunFeiUtils(){}

    @Resource
    private XunFeiProperties xf;
    private static XunFeiProperties xunFeiProperties;

    //注入静态Bean
    @PostConstruct
    public void init() {
        xunFeiProperties=xf;
    }

    //======================================= 基本方法  ================================================================

    //执行语音合成
    /**
     *
     * @param text 文件名
     * @param uri 保存路径
     * @param name 生成文件名
     * @return 生成文件路径
     * @throws Exception exception
     */
    public static String doSynthesize(String text,String uri,String name) throws Exception {
        //验证自定义配置
        if(StringUtils.isNotNull(uri)){
            checkDictionary(uri);
        }

        //默认MP3
        String type=StringUtils.ifNullToString(xunFeiProperties.getType(),"mp3");

        switch (type) {
            case "mp3":
                return doSynthesizeMp3(text, uri, name);
            case "wav":
                return doSynthesizeWav(text, uri, name);
            case "pcm":
                return doSynthesizePcm(text, uri, name);
            default:
                return "";
        }
    }

    //执行语音合成
    public static String doSynthesize(String text,String uri) throws Exception {
        return doSynthesize(text,uri,null);
    }

    //执行语音合成
    public static String doSynthesize(String text) throws Exception {
        return doSynthesize(text,null);
    }

    //======================================= 异步语音合成任务 ==========================================================

    /**
     * @param text 文字   必填
     * @param url  保存路径 必填
     * @throws Exception exception
     */
    public static void doSynthesizeAsync(String text,String url){
        //默认MP3
        String type=StringUtils.ifNullToString(xunFeiProperties.getType(),"mp3");
        try{
            switch (type) {
                case "mp3":
                     doSynthesizeMp3Sync(text,url);
                case "wav":
                     doSynthesizeWavSync(text,url);
                case "pcm":
                     doSynthesizePcmSync(text,url);
            }
        }catch (Exception e){
            //转为非受检异常
            ExceptionUtils.<RuntimeException>throwAs(e);
        }
    }

    //异步任务生成MP3格式
    public static void doSynthesizeMp3Sync(String text, String mp3Url) throws Exception {
        //默认参数生成MP3
        String wavUrl=doSynthesizeWavSync(text);
        //Wav转Mp3格式
        convertWav2Mp3(new File(wavUrl),new File(mp3Url));
    }

    //异步任务执行语音合成
    private static String doSynthesizeWavSync(String text) throws Exception {
        return doSynthesizeWavSync(text,getTempPath("wav"));
    }

    //执行语音合成(生成Wav)
    public static String doSynthesizeWavSync(String text,String wavUrl) throws Exception {
        //默认参数生成pcm
        String pcmUrl= doSynthesizePcmSync(text);
        convertPcm2Wav(pcmUrl,wavUrl);
        return wavUrl;
    }

    //执行语音合成Uri
    private static String doSynthesizePcmSync(String text){
        return doSynthesizePcmSync(text,getTempPath("pcm"));
    }

    //执行语音合成Uri
    public static String doSynthesizePcmSync(String text,String pcmUrl){
        SpeechSynthesizer mTts=initSunthesize();
        //合成文件
        mTts.synthesizeToUri(text,pcmUrl,synthesizeToUriListener);
        try {
            Thread.sleep(100);  //先等待100ms
        }catch (InterruptedException e){
            ExceptionUtils.<RuntimeException>throwAs(e);
        }
        //返回保存路径
        return pcmUrl;
    }

    //========================================== 异步中间文件清理 =======================================================

    //清理中间文件
    /**
     * @param path 最终生成文件指定的路径,可为null,则开启默认清理
     */
    private void CleanTempFile(String path){

    }

    //======================================= MP3语音生成 ===============================================================

    //执行语音合成(生成Mp3)
    public static String doSynthesizeMp3(String text) throws Exception {
        return doSynthesizeMp3(text,null);
    }

    //执行语音合成(生成Mp3)
    public static String doSynthesizeMp3(String text,String uri) throws Exception {
        return doSynthesizeMp3(text, uri,null);
    }

    //执行语音合成(生成Mp3)

    /**
     * @param text 转换的文字(必填)
     * @param uri 保存地址
     * @param name 文件名称
     * @return 生成路径
     * @throws Exception Exception
     */
    public static String doSynthesizeMp3(String text,String uri,String name) throws Exception {
        //验证配置路径
        checkDictionary(xunFeiProperties.getSaveMp3Path());
        //默认参数生成MP3
        String wavUrl=doSynthesizeWav(text);
        //获取路径
        StringBuilder mp3Uri=new StringBuilder(StringUtils.ifNullToString(uri,xunFeiProperties.getSaveMp3Path()));

        if (StringUtils.isNotNull(name)){
            //自定义文件名
            mp3Uri.append(File.separator).append(name).append(".mp3");
        }else{
            //生成文件名
            mp3Uri.append(File.separator).append(getName()).append(".mp3");
        }

        String mp3Url=StringUtils.pathParse(mp3Uri.toString());
        boolean succeed=convertWav2Mp3(new File(wavUrl),new File(mp3Url));
        if(succeed){
            return mp3Url;
        }else{
            return "";
        }

    }

    //======================================= WAV语音生成 ===============================================================

    //执行语音合成(生成Wav)
    private static String doSynthesizeWav(String text,String uri,String name) throws Exception {
        //验证配置路径
        checkDictionary(xunFeiProperties.getSaveWavPath());
        //默认参数生成pcm
        String pcmUrl= doSynthesizePcm(text);

        //获取路径
        StringBuilder wavUri=new StringBuilder(StringUtils.ifNullToString(uri,xunFeiProperties.getSaveWavPath()));

        if (StringUtils.isNotNull(name)){
            //自定义文件名
            wavUri.append(File.separator).append(name).append(".wav");
        }else{
            //生成文件名
            wavUri.append(File.separator).append(getName()).append(".wav");
        }
        String wavUrl=StringUtils.pathParse(wavUri.toString());
        convertPcm2Wav(pcmUrl,wavUrl);
        return wavUrl;
    }

    //执行语音合成(生成Wav)
    private static String doSynthesizeWav(String text,String uri) throws Exception {
       return doSynthesizeWav(text,uri,null);
    }

    //执行语音合成(生成Wav)
    public static String doSynthesizeWav(String text) throws Exception {
        return doSynthesizeWav(text,null);
    }

    //======================================= PCM语音生成 ===============================================================

    //执行语音合成Uri
    public static String doSynthesizePcm(String text){
        return doSynthesizePcm(text,null);
    }

    //执行语音合成Uri
    public static String doSynthesizePcm(String text,String uri){
        return doSynthesizePcm(text,uri,null);
    }

    //执行语音合成Uri
    public static String doSynthesizePcm(String text,String uri,String name){
        //验证配置路径
        checkDictionary(xunFeiProperties.getSavePCMPath());

        //初始化配置
        SpeechSynthesizer mTts=initSunthesize();

        //参数为null则使用配置参数
        StringBuilder pcmUri=new StringBuilder(StringUtils.ifNullToString(uri,xunFeiProperties.getSavePCMPath()));

        if (StringUtils.isNotNull(name)){
            //自定义文件名
            pcmUri.append(File.separator).append(name).append(".pcm");
        }else{
            //生成文件名
            pcmUri.append(File.separator).append(getName()).append(".pcm");
        }
        //路径转换
        String pcmUrl=StringUtils.pathParse(pcmUri.toString());
        //执行语音合成(Windows PCM文件)
        mTts.synthesizeToUri(text,pcmUrl,synthesizeToUriListener);
        try {
            Thread.sleep(100);  //先等待100ms
        }catch (InterruptedException e){
            ExceptionUtils.<RuntimeException>throwAs(e);
        }
        //返回保存路径
        return pcmUrl;
    }

    //=========================================== 格式转换 =============================================================

    //PCM转WAV
    private static void convertPcm2Wav(String src, String target) throws Exception {
        //确认文件是否存在
        File pcmFile=new File(src);
        while(!pcmFile.exists()){    //不存在
            pcmFile=new File(src);
            Thread.sleep(50);  //循环等待 50ms一次
        }
        FileInputStream fis = new FileInputStream(src);
        FileOutputStream fos = new FileOutputStream(target);

        //计算长度
        byte[] buf = new byte[1024 * 4];
        int size = fis.read(buf);
        int PCMSize = 0;
        while (size != -1) {
            PCMSize += size;
            size = fis.read(buf);
        }
        fis.close();

        //填入参数,比特率等等。这里用的是16位单声道 8000 hz
        WaveHeader header = new WaveHeader();
        //长度字段 = 内容的大小(PCMSize) + 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
        header.fileLength = PCMSize + (44 - 8);
        header.FmtHdrLeth = 16;
        header.BitsPerSample = 16;
        header.Channels = 2;
        header.FormatTag = 0x0001;
        header.SamplesPerSec = 8000;
        header.BlockAlign = (short)(header.Channels * header.BitsPerSample / 8);
        header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
        header.DataHdrLeth = PCMSize;

        byte[] h = header.getHeader();

        assert h.length == 44; //WAV标准,头部应该是44字节
        //write header
        fos.write(h, 0, h.length);
        //write data stream
        fis = new FileInputStream(src);
        size = fis.read(buf);
        while (size != -1) {
            fos.write(buf, 0, size);
            size = fis.read(buf);
        }
        fis.close();
        fos.close();
        System.out.println("Convert OK!");
    }

    //wav转MP3
    private static boolean convertWav2Mp3(File source, File target) {
        boolean succeeded = true;
        try {
            AudioAttributes audio = new AudioAttributes();
            audio.setCodec("libmp3lame");
            audio.setBitRate(128000);
            audio.setChannels(2);
            audio.setSamplingRate(44100);
            audio.setVolume(256);

            EncodingAttributes attrs = new EncodingAttributes();
            attrs.setFormat("mp3");
            attrs.setAudioAttributes(audio);
            Encoder encoder = new Encoder();
            encoder.encode(new MultimediaObject(source), target, attrs);
        } catch (Exception ex) {
            ex.printStackTrace();
            succeeded = false;
        }
        return succeeded;
    }


    //=========================================== 文件与路径处理 ========================================================
    //获取保存文件的名称
    private static String getName(){
        return String.valueOf(new Date().getTime());
    }

    //获取最终路径(用于异步任务路径返回及控制)
    public static String getRealPath(String path, String name){
         //默认MP3
        String type=StringUtils.ifNullToString(xunFeiProperties.getType(),"mp3");
        StringBuilder realPath =new StringBuilder("");
        if(type.equals("pcm")){     //pcm
            if (StringUtils.isNotNull(path)){  //路径不为null
                realPath.append(path).append(File.separator);
            }else{
                checkDictionary(xunFeiProperties.getSavePCMPath());
                realPath.append(xunFeiProperties.getSavePCMPath()).append(File.separator);
            }
            if(StringUtils.isNotNull(name)){   //文件名不为空
                realPath.append(name).append(".pcm");
            }else{
                realPath.append(getName()).append(".pcm");
            }
        }else if(type.equals("wav")){   //wav
            if (StringUtils.isNotNull(path)){  //路径不为null
                realPath.append(path).append(File.separator);
            }else{
                checkDictionary(xunFeiProperties.getSaveWavPath());
                realPath.append(xunFeiProperties.getSaveWavPath()).append(File.separator);
            }
            if(StringUtils.isNotNull(name)){   //文件名不为空
                realPath.append(name).append(".wav");
            }else{
                realPath.append(getName()).append(".wav");
            }
        }else{  //mp3
            if (StringUtils.isNotNull(path)){  //路径不为null
                realPath.append(path).append(File.separator);
            }else{
                checkDictionary(xunFeiProperties.getSaveWavPath());
                realPath.append(xunFeiProperties.getSaveMp3Path()).append(File.separator);
            }
            if(StringUtils.isNotNull(name)){   //文件名不为空
                realPath.append(name).append(".mp3");
            }else{
                realPath.append(getName()).append(".mp3");
            }
        }
        return StringUtils.pathParse(realPath.toString());
    }

    //异步任务中间路径生成
    private static String getTempPath(String type){
        if("pcm".equals(type)&&!"pcm".equals(xunFeiProperties.getType())){
            checkDictionary(xunFeiProperties.getSavePCMPath());
            //单行代码拼接String是StringBuilder语法糖,不用StringBuilder
            return StringUtils.pathParse( xunFeiProperties.getSavePCMPath()+File.separator + getName() + ".pcm");
        }else if("wav".equals(type)&&"mp3".equals(xunFeiProperties.getType())){
            checkDictionary(xunFeiProperties.getSaveWavPath());
            return StringUtils.pathParse(xunFeiProperties.getSaveWavPath()+File.separator + getName() + ".wav");
        }else{
            return "";
        }
    }


    //确认目录
    private static void checkDictionary(String uri){
        File file=new File(uri);
        if(!file.exists()){ //不存在创建
            boolean f=file.mkdir();
        }
    }


    //=========================================== 辅助项 ===============================================================

    //初始化
    private static SpeechSynthesizer initSunthesize(){
        //AppId
        SpeechUtility.createUtility(SpeechConstant.APPID +"="+xunFeiProperties.getAppId());
        //创建合成器
        SpeechSynthesizer mTts = SpeechSynthesizer.createSynthesizer();
        //设置合成器参数
        //发音人
        mTts.setParameter(SpeechConstant.VOICE_NAME,xunFeiProperties.getVoiceName());
        //语速,范围0~100
        mTts.setParameter(SpeechConstant.SPEED,xunFeiProperties.getSpeed());
        //语调,范围0~100
        mTts.setParameter(SpeechConstant.PITCH,xunFeiProperties.getPitch());
        //音量,范围0~100
        mTts.setParameter(SpeechConstant.VOLUME,xunFeiProperties.getVolume());
        //文件保存路径
        mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH,xunFeiProperties.getSavePCMPath());
        //可能会对象逃逸,谨慎使用
        return mTts;
    }

    //合成监听器
    private static SynthesizeToUriListener synthesizeToUriListener = new SynthesizeToUriListener() {

        //进度条中
        @Override
        public void onBufferProgress(int progress) {
        }

        //生成成功
        @Override
        public void onSynthesizeCompleted(String uri, SpeechError error) {
            if (error!=null){
                System.out.println(error.getErrorDescription(true));
            }else{
                System.out.println("生成语音成功");
            }
        }

        //监听某一事件
        @Override
        public void onEvent(int i, int i1, int i2, int i3, Object o, Object o1) {
        }

    };

    //用于生成头部信息
    static class WaveHeader {
        public final char[] fileID = {'R', 'I', 'F', 'F'};
        public int fileLength;
        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 char[] wavTag = {'W', 'A', 'V', 'E'};
        public char[] FmtHdrID = {'f', 'm', 't', ' '};
        public int FmtHdrLeth;

        public WaveHeader() {}//无参构造方法

        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 (char c : id) {
                bos.write(c);
            }
        }
    }
    
}

Maven依赖

        <!--讯飞SDK-->
        <dependency>
            <groupId>com.xunfei</groupId>
            <artifactId>sdk</artifactId>
            <version>1.0</version>
            <!--system,类似provided,需要显式提供依赖的jar以后,Maven就不会在Repository中查找它-->
            <scope>system</scope>
            <!--项目根目录下的lib文件夹下-->
            <systemPath>${basedir}/lib/Msc.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>com.xunfei</groupId>
            <artifactId>json</artifactId>
            <version>1.0</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/json-jena-1.0.jar</systemPath>
        </dependency>

        <!--语音格式转换需要的包-->
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-core</artifactId>
            <version>2.4.4</version>
        </dependency>
        <!--mac导入这个-->
        <!--<dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-native-osx64</artifactId>
            <version>2.4.6</version>
        </dependency>-->
        <!--linux导入这个-->
        <!--
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-native-linux64</artifactId>
            <version>2.4.4</version>
        </dependency>
        -->
        <!--widows-->
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-native-win64</artifactId>
            <version>2.4.4</version>
        </dependency>
        <dependency>
            <groupId>com.googlecode.soundlibs</groupId>
            <artifactId>mp3spi</artifactId>
            <version>1.9.5.4</version>
        </dependency>

讯飞配置类

@Data
@Configuration
@ConfigurationProperties("xun-fei")
public class XunFeiProperties {

    //讯飞APPId
    private String appId;

    //发音人
    private String voiceName;

    //设置语速,范围0~100
    private String speed;

    //设置语调,范围0~100
    private String pitch;

    //设置音量,范围0~100
    private String volume;

    //保存路径(PCM)
    private String savePCMPath="";

    //保存路径(Wav)
    private String saveWavPath="";

    //保存路径(Mp3)
    private String saveMp3Path="";

    //在线、离线、混合
    private String engineType;

    //生成文件类型 mp3,pcm和wav
    private String type;

    //播放类型
    private String streamType;

    //背景音乐
    private String backgroundSound;

    //合成音频缓冲时间
    private String ttsBufferTime;

    //采样率,取值{8KHZ,16KHZ}
    private String sampleRate;

}

StringUtils

public class StringUtils {
    private StringUtils(){}
    //判断是否为空字符串或null
    public static boolean isNull(String str){
        return GenericReference.checkObject(str, StringUtils::isNullRef);
    }

    //不为空字符串或null
    public static boolean isNotNull(String str){
        return !isNull(str);
    }

    //字符串为null或空字符串设置为默认值
    public static String ifNullToString(String str,String defaultStr){
        return GenericReference.checkToDefault(str,defaultStr, StringUtils::isNullRef);
    }

    //null转空字符串
    public static String ifNullToString(String str){
        return ifNullToString(str,"");
    }

    //判断是否为空
    private static boolean isNullRef(String str){
        return str==null||"".equals(str);
    }

    //路径字符串转换
    public static String pathParse(String str){
        //防止转义
        return str.replaceAll("/", Matcher.quoteReplacement(File.separator));
    }


}

ExceptionUtils

public class ExceptionUtils {
    private ExceptionUtils(){}

    //受检异常转非受检异常(强制转换法)
    @SuppressWarnings("unchecked")
    public static <T extends RuntimeException> void throwAs(Throwable e) throws T{
        T t =(T)new RuntimeException();
        t.setStackTrace(e.getStackTrace());
        throw t;
    }


    //受检异常转非受检异常(包装法)
    public static void changeToRuntimeException(Throwable e){
        throw new RuntimeException(e);
    }


    //受检异常转非受检异常并输出日志
    @SuppressWarnings("unchecked")
    public static <T extends RuntimeException> void throwAsWithLog(Logger log,Throwable e) throws T{
        log.error(getMessage(e));
        throw (T) e;
    }

    //受检异常转非受检异常并输出日志(包装法)
    public static <T extends RuntimeException> void changeToRuntimeExceptionWithLog(Logger log,Throwable e) throws T{
        log.error(getMessage(e));
        throw new RuntimeException(e);
    }

    //收集详细堆栈信息
    public static String getMessage(Throwable e) {
        StringWriter sw = null;
        PrintWriter pw = null;
        try {
            sw = new StringWriter();
            pw = new PrintWriter(sw);
            // 将出错的栈信息输出到printWriter中
            e.printStackTrace(pw);
            pw.flush();
            sw.flush();
        } finally {
            if (sw != null) {
                try {
                    sw.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (pw != null) {
                pw.close();
            }
        }
        return sw.toString();
    }
}

简单函数式接口封装

public class GenericReference<E> {

    //对E进行判断
    public static <E> boolean checkObject(E e,Predicate<E> predicate){
        return predicate.test(e);
    }

    //判定并转默认
    public static <E> E checkToDefault(E e,E defaultE,Predicate<E> predicate){
        if(predicate.test(e)){
            return defaultE;
        }
        return e;
    }

    //生产者
    public static <E> E get(Supplier<? extends E> supplier){
        return supplier.get();
    }

    //消费者
    public static <E> void consumer(E e,Consumer<? super E> consumer){
        consumer.accept(e);
    }

    //int转E
    public static <E> E intToE(int i, IntFunction<E> intFunction){
        return intFunction.apply(i);
    }

    //E转int
    public static <E> int eToInt(E e, ToIntFunction<E> intFunction){
        return intFunction.applyAsInt(e);
    }

    //对象转换T转换为R
    public static <R,T> R TtoR(T t,Function<T,R> function){
        return function.apply(t);
    }


}

相关文章

网友评论

      本文标题:讯飞语音合成工具类

      本文链接:https://www.haomeiwen.com/subject/zsthcktx.html