帖子

Java通过JNA调So库实现高品质离线语音合成

[复制链接]

该用户从未签到

2100 159****1928 发表于 2020-8-8 22:13:40 1#
1.前面分享了Java通过JNA调dll实现离线命令词识别、离线语音唤醒、普通离线语音合成的功能,本帖给大家分享JNA调so库实现高品质语音合成的方法。
2.Java调dll的代码与调so库的代码很类似,大部分可以复用。提醒大家注意:Windows操作系统下,libName的值是.dll文件去后缀文件名,例如,本文中的DLLMain.dll对应的libName的值是DLLMain。Linux操作系统下,libName的值是.so文件的完整文件名,例如,本文中的hello.so对应的libName是hello.so。不过,若libName写成带.so后缀的形式,那么,在编译.so文件时,文件名需要以lib开头,即libhello.so,对应的libName是hello(注意,不是libhello)。
3.实现步骤如下:
(1)通过下面代码导出可执行的Jar包,并在Jar包的同级目录建立tts文件夹;
(2)把Linux SDK的msc文件、libmsc.so库(x86与x64这两个so库可以重命名与代码中保持一致即可)拷贝到tts文件夹下面;
(3)把以上文件上传至Linux服务器任意文件下,cd到Jar所在目录,执行java -jar xxx.jar(前提Linux要配置JDK环境)即可在Jar同级目录生成tts_sample.wav;
4.提醒使用代码的时候要替换掉自己的appid信息,用到的jna库大家需要自行下载或者通过Maven进行依赖。
5.代码如下:
(1)调用主代码
  1. package main.com.iflytek;
  2. import java.io.BufferedOutputStream;
  3. import java.io.ByteArrayInputStream;
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.File;
  6. import java.io.FileInputStream;
  7. import java.io.FileOutputStream;
  8. import java.io.IOException;
  9. import java.io.InputStream;
  10. import java.util.Arrays;
  11. import java.util.Base64;
  12. import javax.sound.sampled.AudioFormat;
  13. import javax.sound.sampled.AudioInputStream;
  14. import javax.sound.sampled.AudioSystem;
  15. import javax.sound.sampled.DataLine;
  16. import javax.sound.sampled.LineUnavailableException;
  17. import javax.sound.sampled.SourceDataLine;
  18. import com.sun.jna.Library;
  19. import com.sun.jna.Memory;
  20. import com.sun.jna.Native;
  21. import com.sun.jna.Pointer;
  22. import com.sun.jna.ptr.IntByReference;
  23. public class OfflineWindowsTts {
  24.         /*const char *MSPAPI        QTTSSessionBegin(const char *params, int *errorCode)
  25.         开始一次语音合成,分配语音合成资源。
  26.         int MSPAPI        QTTSTextPut(const char *sessionID, const char *textString, unsigned int textLen, const char *params)
  27.         写入要合成的文本。
  28.         const void *MSPAPI        QTTSAudioGet(const char *sessionID, unsigned int *audioLen, int *synthStatus, int *errorCode)
  29.         获取合成音频。
  30.         int MSPAPI        QTTSSessionEnd(const char *sessionID, const char *hints)
  31.         结束本次语音合成。
  32.         int MSPAPI        QTTSGetParam(const char *sessionID, const char *paramName, char *paramValue, unsigned int *valueLen)
  33.         获取当前语音合成信息,如当前合成音频对应文本结束位置、上行流量、下行流量等。*/
  34.         public static String userPath=System.getProperty("java.class.path").replaceAll("\\\\", "/").replace("HighTts.jar", "");
  35.         public interface MyDllInterface extends Library {
  36.                 MyDllInterface INSTANCE = (MyDllInterface)Native.loadLibrary(userPath+"tts/Tts_x64.so", MyDllInterface.class);
  37.                 public int MSPLogin(String usr,String pwd,String params);
  38.                 //文档方法写错了,耽搁一段时间   net.java.dev.jna需要这个包
  39.                 //char * 对应String ;;;int *对应IntByReference
  40.                 String QTTSSessionBegin(String params, IntByReference errorCode);
  41.                 //char * 对应String ;;;int 对应int;;;
  42.                 public int QTTSTextPut(String sessionID,String textString,int textLen,String params);
  43.                 //void * 对应Pointer;;;char *对应String ;;;int * 对应IntByReference;;;
  44.                 Pointer QTTSAudioGet(String sessionID, IntByReference audioLen, IntByReference synthStatus, IntByReference errorCode);
  45.                 //char *对应String;;;
  46.                 public int QTTSSessionEnd(String sessionID, String hints);
  47.                 public int MSPLogout();
  48.         }
  49.         public static void main(String[] args) throws Exception {
  50.                 /*File file=new File(userPath+"tts/Test.txt");
  51.                 System.out.println("当前路径是:"+userPath);*/
  52.                 /*if(file.exists()){
  53.                         System.out.println("文件已存在");
  54.                 }else{
  55.                         boolean flag=file.createNewFile();
  56.                         if(flag){
  57.                                 System.out.println("文件创建成功");
  58.                                 System.out.println(file.getPath());
  59.                         }else{
  60.                                 System.out.println("文件创建失败");
  61.                         }
  62.                 }*/
  63.                 doTts("[p500]浊酒一杯家万里,燕[=yan4]然未勒归无计,科大讯飞是中国最大的语音技术提供商,科大讯飞是中国最大的语音技术提供商");
  64.         }
  65.         public static void doTts(String text) throws Exception{
  66.                 String login_params = "appid = 您的appid, work_dir = "+userPath+"tts";
  67.                 String session_begin_params = "engine_type = purextts,voice_name=xiaoyan, text_encoding = UTF8, tts_res_path = fo|res/xtts/xiaoyan.jet;fo|res/xtts/common.jet, sample_rate = 16000, speed = 50, volume = 50, pitch = 50, rdn = 2";
  68.                 //String session_begin_params ="engine_type = local, voice_name = xiaoyan, text_encoding = GBK, tts_res_path = fo|res\\tts\\xiaoyan.jet;fo|res\\tts\\common.jet, sample_rate = 16000, speed = 50, volume = 50, pitch = 50, rdn = 2";
  69.                 String filename = "tts_sample.wav"; //合成的语音文件名称
  70.                 //String text = "[p500]浊酒一杯家万里,燕[=yan4]然未勒归无计"; //合成文本
  71.                 int ret = MyDllInterface.INSTANCE.MSPLogin(null, null, login_params);
  72.                 if (0!= ret)
  73.                 {
  74.                         System.out.println("MSPLogin failed, error code: %d.\n"+ret);
  75.                 }
  76.                 System.out.println("## 语音合成(Text To Speech,TTS)技术能够自动将任意文字实时转换为连续的 ##");
  77.                 System.out.println("## 自然语音,是一种能够在任何时间、任何地点,向任何人提供语音信息服务的  ##");
  78.                 System.out.println("## 高效便捷手段,非常符合信息时代海量数据、动态更新和个性化查询的需求。  ##");
  79.                 System.out.println("开始合成 ...");
  80.                 IntByReference retOneIntByReference = new IntByReference(-1);
  81.                 String sessionID = MyDllInterface.INSTANCE.QTTSSessionBegin(session_begin_params,retOneIntByReference);
  82.                 System.out.println("sessionID:"+sessionID+";;;C/C++中的文本长度"+text.getBytes().length);
  83.                 //text.length()*3
  84.                 ret= MyDllInterface.INSTANCE.QTTSTextPut(sessionID, text, text.getBytes().length, null);
  85.                 System.out.println("正在合成 ...");
  86.                 int audio_len=1280;
  87.                 int synth_status=1;
  88.                 File file=new File(filename);
  89.                 IntByReference audioLenIntByReference = new IntByReference(0);
  90.                 IntByReference synthStatusIntByReference = new IntByReference(1);
  91.                 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(9600000);
  92.                 int totalAudioLength = 0;
  93.                 int indexFlag=0;
  94.                 //设置音频实时播放参数,
  95.                 /*AudioFormat audioFormat =new AudioFormat(16000F, 16, 1,true,false);*/
  96.                 // 设置数据输入,开启播放监听
  97.                 /*DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class,audioFormat, AudioSystem.NOT_SPECIFIED);
  98.                 SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
  99.                 sourceDataLine.open(audioFormat);
  100.                 sourceDataLine.start();*/
  101.                 while (true) {
  102.                         /* 获取合成音频 */
  103.                         //System.err.println("即将发生错误");
  104.                         //const void *不知道怎么转换
  105.                         //System.err.println(ret);
  106.                         Pointer dataPoint = MyDllInterface.INSTANCE.QTTSAudioGet(sessionID,audioLenIntByReference,synthStatusIntByReference,retOneIntByReference);
  107.                         if (0!= ret){
  108.                                 System.err.println(ret);
  109.                                 break;
  110.                         }
  111.                         //System.err.println(retOneIntByReference.getValue());
  112.                         byte[] dataByteAudio;
  113.                         if (dataPoint!=null ){
  114.                                 dataByteAudio = dataPoint.getByteArray(0, audioLenIntByReference.getValue());
  115.                                 try{
  116.                                         //System.out.println("本次获得音频长度"+audioLenIntByReference.getValue());
  117.                                         //System.out.println(dataByteAudio.toString());
  118.                                         byteArrayOutputStream.write(dataByteAudio,0,audioLenIntByReference.getValue());
  119.                                         //每次合成都写入到实时播放的线程中...
  120.                                         //sourceDataLine.write(dataByteAudio, 0, audioLenIntByReference.getValue());        
  121.                                 }catch(Exception e){
  122.                                         e.printStackTrace();
  123.                                 }
  124.                                 indexFlag=indexFlag+audioLenIntByReference.getValue();
  125.                                 totalAudioLength=totalAudioLength+audioLenIntByReference.getValue();
  126.                         }
  127.                         //System.err.println(synthStatusIntByReference.getValue());
  128.                         if (synthStatusIntByReference.getValue()==2){
  129.                                 break;
  130.                         }
  131.                         //Thread.sleep(50);
  132.                 }
  133.                 // 清空数据缓冲,并关闭输入。实时播放完毕,关闭播放流
  134.                 /*sourceDataLine.drain();
  135.                 sourceDataLine.close();*/
  136.                 byteArrayOutputStream.flush();
  137.                 byteArrayOutputStream.close();
  138.                 //可以把字节数组进行Base64加密打印
  139.                 //然后再解密写进去
  140.                 //System.err.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
  141.                 //System.err.println(byteArrayOutputStream.toByteArray());
  142.                 //直接通过FileOutputStream写字节数字到文件也是可以的~
  143.                 /*File f = new File("./zMusic/pcm/Tts/在线语音合成.pcm");
  144.                 FileOutputStream os = new FileOutputStream(f);
  145.                 os.write(byteArrayOutputStream.toByteArray());
  146.                 os.flush();*/
  147.                 WaveHeader.DataSize = WaveHeader.revers(WaveHeader.intToBytes(totalAudioLength));
  148.                 WaveHeader.RIFF_SIZE = WaveHeader.revers(WaveHeader.intToBytes(totalAudioLength + 36 - 8));
  149.                 File wavfile = new File("./tts_sample.wav");
  150.                 FileOutputStream fileOutputStream = null;
  151.                 try {
  152.                         fileOutputStream = new FileOutputStream(wavfile);
  153.                         BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
  154.                         WaveHeader.init();
  155.                         bufferedOutputStream.write(WaveHeader.RIFF);
  156.                         bufferedOutputStream.write(WaveHeader.RIFF_SIZE);
  157.                         bufferedOutputStream.write(WaveHeader.RIFF_TYPE);
  158.                         bufferedOutputStream.write(WaveHeader.FORMAT);
  159.                         bufferedOutputStream.write(WaveHeader.FORMAT_SIZE);
  160.                         bufferedOutputStream.write(WaveHeader.FORMAT_TAG);
  161.                         bufferedOutputStream.write(WaveHeader.CHANNELS);
  162.                         bufferedOutputStream.write(WaveHeader.SamplesPerSec);
  163.                         bufferedOutputStream.write(WaveHeader.AvgBytesPerSec);
  164.                         bufferedOutputStream.write(WaveHeader.BlockAlign);
  165.                         bufferedOutputStream.write(WaveHeader.BitsPerSample);

  166.                         bufferedOutputStream.write(WaveHeader.Data);
  167.                         bufferedOutputStream.write(WaveHeader.DataSize);
  168.                         bufferedOutputStream.write(byteArrayOutputStream.toByteArray());
  169.                         bufferedOutputStream.flush();
  170.                         bufferedOutputStream.close();
  171.                 } catch (IOException e) {
  172.                         // TODO Auto-generated catch block
  173.                         e.printStackTrace();
  174.                 }
  175.                 // 合成完毕
  176.                 retOneIntByReference.setValue(MyDllInterface.INSTANCE.QTTSSessionEnd(sessionID, "正常退出"));
  177.                 if (0!= retOneIntByReference.getValue()) {
  178.                         System.out.println("QTTSSessionEnd failed, error code:" + retOneIntByReference.getValue());
  179.                 }
  180.                 System.out.println("合成完毕");
  181.                 //退出
  182.                 MyDllInterface.INSTANCE.MSPLogout();
  183.                 /*//无需使用下面的方法,实时播放
  184.                 PlayWav playWav=new PlayWav("./tts_sample.wav");
  185.                 playWav.play();*/
  186.         }
  187. }
复制代码

(2)实现保存为wav的代码
  1. package main.com.iflytek;
  2. public class WaveHeader {
  3.     public static byte[] RIFF = "RIFF".getBytes();
  4.     public static byte[] RIFF_SIZE = new byte[8];
  5.     public static byte[] RIFF_TYPE = "WAVE".getBytes();
  6.     public static byte[] FORMAT = "fmt ".getBytes();
  7.     public static byte[] FORMAT_SIZE = new byte[4];
  8.     public static byte[] FORMAT_TAG = new byte[2];
  9.     public static byte[] CHANNELS = new byte[2];
  10.     public static byte[] SamplesPerSec = new byte[4];
  11.     public static byte[] AvgBytesPerSec = new byte[4];
  12.     public static byte[] BlockAlign = new byte[2];
  13.     public static byte[] BitsPerSample = new byte[2];
  14.     public static byte[] Data = "data".getBytes();
  15.     public static byte[] DataSize = new byte[4];
  16.     public static void init() {
  17.         FORMAT_SIZE = new byte[]{(byte) 16, (byte) 0, (byte) 0, (byte) 0};
  18.         byte[] tmp = revers(intToBytes(1));
  19.         FORMAT_TAG = new byte[]{tmp[0], tmp[1]};
  20.         CHANNELS = new byte[]{tmp[0], tmp[1]};
  21.         SamplesPerSec = revers(intToBytes(16000));
  22.         AvgBytesPerSec = revers(intToBytes(32000));
  23.         tmp = revers(intToBytes(2));
  24.         BlockAlign = new byte[]{tmp[0], tmp[1]};
  25.         tmp = revers(intToBytes(16));
  26.         BitsPerSample = new byte[]{tmp[0], tmp[1]};
  27.     }
  28.     public static byte[] revers(byte[] tmp) {
  29.         byte[] reversed = new byte[tmp.length];
  30.         for (int i = 0; i < tmp.length; i++) {
  31.             reversed[i] = tmp[tmp.length - i - 1];
  32.         }
  33.         return reversed;
  34.     }
  35.     public static byte[] intToBytes(int num) {
  36.         byte[] bytes = new byte[4];
  37.         bytes[0] = (byte) (num >> 24);
  38.         bytes[1] = (byte) ((num >> 16) & 0x000000FF);
  39.         bytes[2] = (byte) ((num >> 8) & 0x000000FF);
  40.         bytes[3] = (byte) (num & 0x000000FF);
  41.         return bytes;
  42.     }
  43. }
复制代码

想必通过上面的介绍,大家已经掌握了Java调so库实现高品质离线语音合成的方法,如果还有疑惑或者问题可以通过评论区发表你的问题~~~