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

讯飞语音合成工具类

作者: 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