1、离线语音合成调用主函数(离线语音合成调用属于简单的,无回调函数)
package com.iflytek;
import com.iflytek.util.Step2_tts_thread;
import com.iflytek.util.Step3_audioFormat;
import java.util.Scanner;
import javax.sound.sampled.*;
/**
* 请注意!!!
* 1.首选到控制台https://console.xfyun.cn/services/aisound下载普通离线语音合成的Windows MSC。
* 2.普通离线语音合成Windows MSC解压后,把bin目录下msc文件夹与dll文件拷贝到res目录下。
* 3.最后请替换Step2_tts_thread中的appid值,appid值在下载页面控制台可以看到。
*/
public class OfflineTtsMain {
//录音相关参数
public static AudioFormat audioFormat;
public static SourceDataLine sourceDataLine;
public static void main(String[] args) throws Exception {
System.out.println("输入y开始体验普通离线语音合成...");
Scanner myScanner = new Scanner(System.in);
String userCommand = myScanner.next();
long startTime = System.currentTimeMillis();
if(userCommand.equals("y")){
audioFormat = Step3_audioFormat.getAudioFormat(audioFormat);//构造具有线性 PCM 编码和给定参数的 AudioFormat。
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class,audioFormat, AudioSystem.NOT_SPECIFIED);
sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
Step2_tts_thread myThread=new Step2_tts_thread();
myThread.start();
}
}
}
2、根据MSC文档用JAVA重写方法
package com.iflytek.service;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
public interface Step1_tts_dll extends Library {
/**
* 重点:
* 1.char * 对应 String
* 2.int * 对应 IntByReference
* 3.void * 对应 byte[]/Pointer,回调函数里此类型需用String来对应。
* 4.int 对应 int
* 5.无参 对应 void
* 6.回调函数 对应 根据文档自定义回调函数,实现接口Callback,离线语音合成无回调
*/
//加载dll动态库并实例化,从而使用其内部的方法
Step1_tts_dll INSTANCE = (Step1_tts_dll) Native.loadLibrary("res/msc_x64.dll", Step1_tts_dll.class);
//定义登录方法
public int MSPLogin(String usr, String pwd, String params);
//开始一次普通离线语音合成
public String QTTSSessionBegin(String params, IntByReference errorCode);
//写入需要合成的文本
public int QTTSTextPut(String sessionID,String textString,int textLen,String params);
//获取离线合成的音频
public Pointer QTTSAudioGet(String sessionID, IntByReference audioLen, IntByReference synthStatus, IntByReference errorCode);
//结束本次普通离线语音合成
public int QTTSSessionEnd(String sessionID, String hints);
//定义退出方法
public int MSPLogout();
}
3、把调用步骤依次写进线程类
package com.iflytek.util;
import com.iflytek.OfflineTtsMain;
import com.iflytek.service.Step1_tts_dll;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import java.io.ByteArrayOutputStream;
import java.util.Scanner;
public class Step2_tts_thread extends Thread{
public static int total_audio_length=0;
public static ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
public void run() {
while (true) {
//登录参数
String login_params = "appid = 替换你的appid, work_dir = ./res";
//第一个参数为用户名,第二个参数为密码,传null即可
int ret = Step1_tts_dll.INSTANCE.MSPLogin(null, null, login_params);
if (ret != 0) {//登录成功标志ret为0
System.out.println("登录失败...请检查");
System.exit(1);
} else {
//System.out.println("登录成功...");
}
//请让一让,A02-02-03
//开始一次普通离线语音合成会话
String session_begin_params = "engine_type = local, voice_name = xiaoyan, text_encoding = UTF8, tts_res_path = fo|res/tts/xiaoyan.jet;fo|res/tts/common.jet, sample_rate = 16000, speed = 50, volume = 50, pitch = 50, rdn = 2";
IntByReference errorCode = new IntByReference(-100);
String session_id = Step1_tts_dll.INSTANCE.QTTSSessionBegin(session_begin_params,errorCode);
if(errorCode.getValue()==0){
//System.out.println("开启普通离线语音合成成功,session_id是:"+session_id);
}else{
System.out.println("开启普通离线语音合成失败:"+errorCode.getValue());
}
//写入需要合成的文本
System.out.println("\033[43;34;4m"+"请输入你要合成的文本并回车"+"\033[0m");
Scanner myScanner = new Scanner(System.in);
String tts_text =myScanner.nextLine();
//System.out.println(tts_text);//注意获取的文本是否完整
ret= Step1_tts_dll.INSTANCE.QTTSTextPut(session_id, tts_text, tts_text.getBytes().length,null);
if(ret==0){
//System.out.println("写入合成文本成功...");
}else{
System.out.println("写入合成文本失败:"+ret);
}
//循环获取离线合成的音频,并实时进行播放
IntByReference audio_len=new IntByReference(-100);
IntByReference synth_status=new IntByReference(-100);
errorCode=new IntByReference(-100);
try{
//实时播放
OfflineTtsMain.sourceDataLine.open(OfflineTtsMain.audioFormat);
OfflineTtsMain.sourceDataLine.start();
}catch (Exception e){
e.printStackTrace();
}
//int i=0;
while (true) {
Pointer audioPointer = Step1_tts_dll.INSTANCE.QTTSAudioGet(session_id,audio_len,synth_status,errorCode);
byte[] audioDataByteArray=null;
if(audioPointer!=null){
audioDataByteArray=audioPointer.getByteArray(0,audio_len.getValue());
}
if (errorCode.getValue()==0){
//System.out.println("正常获取音频中...");
}else{
System.out.println("获取音频发生错误:"+errorCode);
break;
}
if (audioDataByteArray!=null ){
try{
//实时播放
OfflineTtsMain.sourceDataLine.write(audioDataByteArray, 0, audio_len.getValue());
/*i=i+1;//实现暂停的效果
System.out.println(i);
if(i==5){
OfflineTtsMain.sourceDataLine.stop();
Thread.sleep(5000);
}
if(i==6){
OfflineTtsMain.sourceDataLine.start();
}*/
//将数据写入字节数组的输出流,用来生成音频文件
byteArrayOutputStream.write(audioDataByteArray, 0, audio_len.getValue());
}catch(Exception e){
e.printStackTrace();
}
//计算总音频长度,用来生成音频文件
total_audio_length=total_audio_length+audio_len.getValue();
}
if (synth_status.getValue()==2){
//说明音频已经取完,退出本次循环
try{
OfflineTtsMain.sourceDataLine.drain();
OfflineTtsMain.sourceDataLine.close();
byteArrayOutputStream.flush();
byteArrayOutputStream.close();
}catch (Exception e){
e.printStackTrace();
}
break;
}
}
//把合成的音频存放为wav格式
String dateAsFileName= String.valueOf(System.currentTimeMillis());//用当前时间标记文件名
Boolean waveProductFlag=Step4_wave_product.waveProduct(total_audio_length,byteArrayOutputStream,"./res/"+dateAsFileName+".wav");
if(waveProductFlag){
System.out.println("生成音频文件成功:"+dateAsFileName+".wav");
}else{
System.out.println("生成音频文件失败...");
}
//结束本次普通离线语音合成
ret=Step1_tts_dll.INSTANCE.QTTSSessionEnd(session_id, "正常退出");
if(ret==0){
//System.out.println("离线语音合成正常退出...");
}else{
System.out.println("离线语音合成退出异常:"+ret);
}
//执行最终退出
ret = Step1_tts_dll.INSTANCE.MSPLogout();
if (ret == 0) {
//System.out.println("正常退出...");
} else {
System.out.println("异常退出:"+ret);
}
//是否退出Java程序
System.out.println("\33[43;31;4m"+"结束合成:输入n,继续合成:输入其他内容"+"\33[0m");
Scanner myScanner2 = new Scanner(System.in);
String userCommand = myScanner2.nextLine();
if(userCommand.equals("n")){
OfflineTtsMain.sourceDataLine.stop();
OfflineTtsMain.sourceDataLine.close();
System.exit(0);
}
}
}
}
4、生成16K、16BIT单声道音频流
package com.iflytek.util;
import javax.sound.sampled.AudioFormat;
public class Step3_audioFormat {
//构造线程参数
//16k采样率的16bit音频,一帧的大小为640B, 时长20ms
/**
sampleRate - 每秒样品数
sampleSizeInBits - 每个样本中的位数
channels - 通道数(1为mono,2为立体声等)
signed - 表示数据是签名还是无符号
bigEndian - 指示单个样本的数据是否以大字节顺序存储( false表示小端)
*/
public static AudioFormat getAudioFormat(AudioFormat audioFormat) {
audioFormat=new AudioFormat(16000F, 16, 1,true,false);
// true,false 指示是以 big-endian 顺序还是以 little-endian 顺序存储音频数据。
return audioFormat;//构造具有线性 PCM 编码和给定参数的 AudioFormat。
}
}
5、生成WAV音频所需的文件头
package com.iflytek.util;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
public class Step4_wave_product {
public static byte[] RIFF = "RIFF".getBytes();
public static byte[] RIFF_SIZE = new byte[8];
public static byte[] RIFF_TYPE = "WAVE".getBytes();
public static byte[] FORMAT = "fmt ".getBytes();
public static byte[] FORMAT_SIZE = new byte[4];
public static byte[] FORMAT_TAG = new byte[2];
public static byte[] CHANNELS = new byte[2];
public static byte[] SamplesPerSec = new byte[4];
public static byte[] AvgBytesPerSec = new byte[4];
public static byte[] BlockAlign = new byte[2];
public static byte[] BitsPerSample = new byte[2];
public static byte[] Data = "data".getBytes();
public static byte[] DataSize = new byte[4];
public static boolean waveProduct(int audioLength, ByteArrayOutputStream byteArrayOutputStream, String outputFilePath){
Step4_wave_product.DataSize = Step4_wave_product.revers(Step4_wave_product.intToBytes(audioLength));
Step4_wave_product.RIFF_SIZE = Step4_wave_product.revers(Step4_wave_product.intToBytes(audioLength + 36 - 8));
File waveFile = new File(outputFilePath);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(waveFile);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
init();
bufferedOutputStream.write(RIFF);
bufferedOutputStream.write(RIFF_SIZE);
bufferedOutputStream.write(RIFF_TYPE);
bufferedOutputStream.write(FORMAT);
bufferedOutputStream.write(FORMAT_SIZE);
bufferedOutputStream.write(FORMAT_TAG);
bufferedOutputStream.write(CHANNELS);
bufferedOutputStream.write(SamplesPerSec);
bufferedOutputStream.write(AvgBytesPerSec);
bufferedOutputStream.write(BlockAlign);
bufferedOutputStream.write(BitsPerSample);
bufferedOutputStream.write(Data);
bufferedOutputStream.write(DataSize);
bufferedOutputStream.write(byteArrayOutputStream.toByteArray());
bufferedOutputStream.flush();
bufferedOutputStream.close();
return true;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
public static void init() {
FORMAT_SIZE = new byte[]{(byte) 16, (byte) 0, (byte) 0, (byte) 0};
byte[] tmp = revers(intToBytes(1));
FORMAT_TAG = new byte[]{tmp[0], tmp[1]};
CHANNELS = new byte[]{tmp[0], tmp[1]};
SamplesPerSec = revers(intToBytes(16000));
AvgBytesPerSec = revers(intToBytes(32000));
tmp = revers(intToBytes(2));
BlockAlign = new byte[]{tmp[0], tmp[1]};
tmp = revers(intToBytes(16));
BitsPerSample = new byte[]{tmp[0], tmp[1]};
}
public static byte[] revers(byte[] tmp) {
byte[] reversed = new byte[tmp.length];
for (int i = 0; i < tmp.length; i++) {
reversed[i] = tmp[tmp.length - i - 1];
}
return reversed;
}
public static byte[] intToBytes(int num) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (num >> 24);
bytes[1] = (byte) ((num >> 16) & 0x000000FF);
bytes[2] = (byte) ((num >> 8) & 0x000000FF);
bytes[3] = (byte) (num & 0x000000FF);
return bytes;
}
}
6、放置讯飞开放平台下载Linux/Windows中所带的资源
7、Linux代码基本类似,离线合成普通版,注意资源放置
package com.iflytek.service;
import com.iflytek.OfflineTtsMain;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
public interface Step1_tts_so extends Library {
/**
* 重点:
* 1.char * 对应 String
* 2.int * 对应 IntByReference
* 3.void * 对应 byte[]/Pointer,回调函数里此类型需用String来对应。
* 4.int 对应 int
* 5.无参 对应 void
* 6.回调函数 对应 根据文档自定义回调函数,实现接口Callback,离线语音合成无回调
*/
//加载dll动态库并实例化,从而使用其内部的方法
Step1_tts_so INSTANCE = (Step1_tts_so) Native.loadLibrary(OfflineTtsMain.userPath+"res/libmsc.so", Step1_tts_so.class);
//定义登录方法
public int MSPLogin(String usr, String pwd, String params);
//开始一次普通离线语音合成
public String QTTSSessionBegin(String params, IntByReference errorCode);
//写入需要合成的文本
public int QTTSTextPut(String sessionID,String textString,int textLen,String params);
//获取离线合成的音频
public Pointer QTTSAudioGet(String sessionID, IntByReference audioLen, IntByReference synthStatus, IntByReference errorCode);
//结束本次普通离线语音合成
public int QTTSSessionEnd(String sessionID, String hints);
//定义退出方法
public int MSPLogout();
}
8、Linux代码基本类似,离线合成高品质,注意资源放置
package com.iflytek.service;
import com.iflytek.OfflineHighQualityTtsMain;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
public interface Step1_tts_so extends Library {
/**
* 重点:
* 1.char * 对应 String
* 2.int * 对应 IntByReference
* 3.void * 对应 byte[]/Pointer,回调函数里此类型需用String来对应。
* 4.int 对应 int
* 5.无参 对应 void
* 6.回调函数 对应 根据文档自定义回调函数,实现接口Callback,离线语音合成无回调
*/
//加载so动态库并实例化,从而使用其内部的方法
Step1_tts_so INSTANCE = (Step1_tts_so) Native.loadLibrary(OfflineHighQualityTtsMain.userPath+"res/libmsc.so", Step1_tts_so.class);
//定义登录方法
public int MSPLogin(String usr, String pwd, String params);
//开始一次高品质离线语音合成
public String QTTSSessionBegin(String params, IntByReference errorCode);
//写入需要合成的文本
public int QTTSTextPut(String sessionID,String textString,int textLen,String params);
//获取离线合成的音频
public Pointer QTTSAudioGet(String sessionID, IntByReference audioLen, IntByReference synthStatus, IntByReference errorCode);
//结束本次高品质离线语音合成
public int QTTSSessionEnd(String sessionID, String hints);
//定义退出方法
public int MSPLogout();
}
9、普通版与高品质版对内存的消耗也不同,请根据实际所需进行选择