前言:
语音合成:
与语音听写相反,语音合成是将一段文字转换为语音,可根据需要合成出不同音色、语速和语调的声音,让机器像人一样开口说话
效果图:
2、直接上代码,配置不再重复说明了:
①、TTSActivity.java
public class TTSActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "TTSActivity";
// 语音合成对象
private SpeechSynthesizer mTts;
// 默认发音人
private String voicer = "xiaoyan";
//云端发音人名称列表(中文)
private String[] mCloudVoicersEntries;
//云端发音人名称列表(英文简称)
private String[] mCloudVoicersValue;
//音频流名称列表
private String[] mCloudVoicersName;
// 缓冲进度
private int mPercentForBuffering = 0;
// 播放进度
private int mPercentForPlaying = 0;
// 引擎类型
private String mEngineType = SpeechConstant.TYPE_CLOUD;
private MemoryFile memFile;
public volatile long mTotalSize = 0;
private Vector<byte[]> container = new Vector<>();
private TextView voice_value1, voice_value2, voice_value3, progress;
private SeekBar seekBar1, seekBar2, seekBar3;
private Button ttsBtnPersonType,ttsBtnPersonSelect;
//起始值50
private int voiceNumber1 = 50, voiceNumber2 = 50, voiceNumber3 = 50;
//播报的文字
private EditText ttsText;
//音频流类型
private String voiceName;
private SpannableStringBuilder builder;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tts);
//初始化控件
findViewById(R.id.tts_play).setOnClickListener(this);
findViewById(R.id.tts_cancel).setOnClickListener(this);
findViewById(R.id.tts_pause).setOnClickListener(this);
findViewById(R.id.tts_resume).setOnClickListener(this);
voice_value1 = this.findViewById(R.id.voice_value1);
voice_value2 = this.findViewById(R.id.voice_value2);
voice_value3 = this.findViewById(R.id.voice_value3);
seekBar1 = this.findViewById(R.id.seek_bar1);
seekBar2 = this.findViewById(R.id.seek_bar2);
seekBar3 = this.findViewById(R.id.seek_bar3);
ttsText = this.findViewById(R.id.tts_text);
progress = this.findViewById(R.id.progress);
ttsBtnPersonType = this.findViewById(R.id.tts_btn_person_type);
ttsBtnPersonSelect = this.findViewById(R.id.tts_btn_person_select);
ttsBtnPersonType.setOnClickListener(this);
ttsBtnPersonSelect.setOnClickListener(this);
//seekbar滑动事件
initSeekBarListener();
// 初始化合成对象
mTts = SpeechSynthesizer.createSynthesizer(this, mTtsInitListener);
mCloudVoicersEntries = getResources().getStringArray(R.array.voicer_cloud_entries);
mCloudVoicersValue = getResources().getStringArray(R.array.voicer_cloud_values);
mCloudVoicersName = getResources().getStringArray(R.array.stream_entries);
}
private void initSeekBarListener() {
//语速
seekBar1.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//进度发生改变时会触发
voice_value1.setText("语速:" + i);
voiceNumber1 = i;
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
//按住SeekBar时会触发
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
//放开SeekBar时触发
}
});
//音调
seekBar2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
voice_value2.setText("音调:" + i);
voiceNumber2 = i;
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
//音量
seekBar3.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
voice_value3.setText("音量:" + i);
voiceNumber3 = i;
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
@Override
public void onClick(View view) {
if (null == mTts) {
// 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
showToast("创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化");
return;
}
switch (view.getId()) {
// 开始合成
// 收到onCompleted 回调时,合成结束、生成合成音频
// 合成的音频格式:只支持pcm格式
case R.id.tts_play:
//恢复背景色为白色
setTextStyle(0, ttsText.getText().toString().length(), Color.WHITE);
// 设置参数
setParam();
/**
* 只保存音频不进行播放接口,调用此接口请注释startSpeaking接口
* text:要合成的文本,uri:需要保存的音频全路径,listener:回调接口
*/
int code = mTts.startSpeaking(ttsText.getText().toString(), mTtsListener);
//String path = Environment.getExternalStorageDirectory() + "/tts.pcm";
//int code = mTts.synthesizeToUri(texts, path, mTtsListener);
if (code != ErrorCode.SUCCESS) {
showToast("语音合成失败,错误码: " + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
}
break;
// 取消合成
case R.id.tts_cancel:
//恢复背景色为白色
setTextStyle(0, ttsText.getText().toString().length(), Color.WHITE);
mTts.stopSpeaking();
break;
// 暂停播放
case R.id.tts_pause:
mTts.pauseSpeaking();
break;
// 继续播放
case R.id.tts_resume:
mTts.resumeSpeaking();
break;
// 选择发音人
case R.id.tts_btn_person_select:
showPresonSelectDialog();
break;
// 选择音频流类型
case R.id.tts_btn_person_type:
showVoiceSelectDialog();
break;
}
}
private int selectedNum = 0;
private int selectedVoiceNum = 0;
/**
* 发音人选择。
*/
private void showPresonSelectDialog() {
new AlertDialog.Builder(this).setTitle("在线合成发音人选项")
// 单选框有几项,各是什么名字
.setSingleChoiceItems(mCloudVoicersEntries, selectedNum,
new DialogInterface.OnClickListener() { // 点击单选框后的处理
public void onClick(DialogInterface dialog, int which) { // 点击了哪一项
voicer = mCloudVoicersValue[which];
//凯瑟琳、亨利(阿森纳)、玛丽切换文本至英文
if ("catherine".equals(voicer) || "henry".equals(voicer) || "vimary".equals(voicer)) {
ttsText.setText(R.string.text_tts_source_en);
} else {
//其他中文
ttsText.setText(R.string.text_tts_source);
}
ttsBtnPersonSelect.setText("发音人:"+mCloudVoicersEntries[which]);
selectedNum = which;
dialog.dismiss();
}
}).show();
}
/**
* 音频流类型选择
*/
private void showVoiceSelectDialog() {
new AlertDialog.Builder(this).setTitle("在线合成发音人选项")
// 单选框有几项,各是什么名字
.setSingleChoiceItems(mCloudVoicersName, selectedVoiceNum,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
voiceName = mCloudVoicersName[which];
ttsBtnPersonType.setText("音频流类型:" + voiceName);
dialog.dismiss();
}
}).show();
}
/**
* 初始化监听。
*/
private InitListener mTtsInitListener = new InitListener() {
@Override
public void onInit(int code) {
Log.e(TAG, "InitListener init() code = " + code);
if (code != ErrorCode.SUCCESS) {
showToast("初始化失败,错误码:" + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
} else {
// 初始化成功,之后可以调用startSpeaking方法
// 注:有的开发者在onCreate方法中创建完合成对象之后马上就调用startSpeaking进行合成,
// 正确的做法是将onCreate中的startSpeaking调用移至这里
}
}
};
/**
* 合成回调监听。
*/
private SynthesizerListener mTtsListener = new SynthesizerListener() {
@Override
public void onSpeakBegin() {
showToast("开始播放");
}
@Override
public void onSpeakPaused() {
showToast("暂停播放");
}
@Override
public void onSpeakResumed() {
showToast("继续播放");
}
@Override
public void onBufferProgress(int percent, int beginPos, int endPos, String info) {
// 合成进度
Log.e(TAG, "onBufferProgress percent =" + percent);
//记录暂停时的进度
mPercentForBuffering = percent;
//缓冲与播放进度
showToast(String.format(getString(R.string.tts_toast_format), mPercentForBuffering, mPercentForPlaying));
}
@Override
public void onSpeakProgress(int percent, int beginPos, int endPos) {
// 播放进度
Log.e(TAG, "onSpeakProgress percent =" + percent);
mPercentForPlaying = percent;
progress.setText("当前进度:" + percent + "%");
setTextStyle(beginPos, endPos, Color.GREEN);
}
@Override
public void onCompleted(SpeechError error) {
if (error == null) {
//showToast("播放完成");
Log.e(TAG, "onCompleted: 播放完成" + container.size());
try {
for (int i = 0; i < container.size(); i++) {
writeToFile(container.get(i));
}
} catch (IOException e) {
}
//播放完成后保存至文件夹
FileUtil.saveFile(memFile, mTotalSize, Environment.getExternalStorageDirectory() + "/helloword-Tts1.pcm");
} else if (error != null) {
showToast(error.getPlainDescription(true));
}
}
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
// 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
// 若使用本地能力,会话id为null
if (SpeechEvent.EVENT_SESSION_ID == eventType) {
String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
Log.e(TAG, "onEventid =" + sid);
}
//当设置SpeechConstant.TTS_DATA_NOTIFY为1时,抛出buf数据
if (SpeechEvent.EVENT_TTS_BUFFER == eventType) {
byte[] buf = obj.getByteArray(SpeechEvent.KEY_EVENT_TTS_BUFFER);
Log.e(TAG, "onEvent bufis =" + buf.length);
container.add(buf);
}
}
};
/**
* 设置文字背景色
*/
private void setTextStyle(int beginPos, int endPos, int color) {
builder = new SpannableStringBuilder(ttsText.getText());
///当前播放的文字背景设置为蓝色
builder.setSpan(new BackgroundColorSpan(color), beginPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ttsText.setText(builder);
Log.e(TAG, "beginPos = " + beginPos + " endPos = " + endPos);
}
/**
* 参数设置
*/
private void setParam() {
// 清空参数
mTts.setParameter(SpeechConstant.PARAMS, null);
// 根据合成引擎设置相应参数
if (mEngineType.equals(SpeechConstant.TYPE_CLOUD)) {
mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
//支持实时音频返回,仅在synthesizeToUri条件下支持
mTts.setParameter(SpeechConstant.TTS_DATA_NOTIFY, "1");
// mTts.setParameter(SpeechConstant.TTS_BUFFER_TIME,"1");
// 设置在线合成发音人
mTts.setParameter(SpeechConstant.VOICE_NAME, voicer);
//设置合成语速(String 类型)
mTts.setParameter(SpeechConstant.SPEED, voiceNumber1 + "");
//设置合成音调
mTts.setParameter(SpeechConstant.PITCH, voiceNumber2 + "");
//设置合成音量
mTts.setParameter(SpeechConstant.VOLUME, voiceNumber3 + "");
} else {
mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
mTts.setParameter(SpeechConstant.VOICE_NAME, "");
}
//设置播放器音频流类型
mTts.setParameter(SpeechConstant.STREAM_TYPE, voiceName);
// 设置播放合成音频打断音乐播放,默认为true
mTts.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "false");
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
mTts.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/helloword-Tts2.wav");
}
@Override
protected void onDestroy() {
super.onDestroy();
if (null != mTts) {
mTts.stopSpeaking();
// 退出时释放连接
mTts.destroy();
}
}
@Override
protected void onResume() {
//移动数据统计分析
/*FlowerCollector.onResume(TtsDemo.this);
FlowerCollector.onPageStart(TAG);*/
super.onResume();
}
@Override
protected void onPause() {
//移动数据统计分析
/*FlowerCollector.onPageEnd(TAG);
FlowerCollector.onPause(TtsDemo.this);*/
super.onPause();
}
private void writeToFile(byte[] data) throws IOException {
if (data == null || data.length == 0)
return;
try {
if (memFile == null) {
Log.e(TAG, "writeToFile: 写入文件夹");
String mFilepath = Environment.getExternalStorageDirectory() + "/helloword-Tts1.pcm";
memFile = new MemoryFile(mFilepath, 1920000);
memFile.allowPurging(false);
}
memFile.writeBytes(data, 0, (int) mTotalSize, data.length);
mTotalSize += data.length;
} finally {
}
}
/**
* 展示吐司
*/
private void showToast(final String str) {
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
}
②、对应布局文件activity_tts.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="讯飞合成示例"
android:textSize="30sp" />
<EditText
android:id="@+id/tts_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="@string/text_tts_source"
android:textSize="20sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/voice_value1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="语速:50"
android:textSize="18sp" />
<SeekBar
android:id="@+id/seek_bar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:maxHeight="2dp"
android:padding="5dp"
android:progress="50"
android:thumb="@drawable/setting_p" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp">
<TextView
android:id="@+id/voice_value2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="音调:50"
android:textSize="18sp" />
<SeekBar
android:id="@+id/seek_bar2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:maxHeight="2dp"
android:padding="5dp"
android:progress="50"
android:thumb="@drawable/setting_p" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp">
<TextView
android:id="@+id/voice_value3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="音量:50"
android:textSize="18sp" />
<SeekBar
android:id="@+id/seek_bar3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:maxHeight="2dp"
android:padding="5dp"
android:progress="50"
android:thumb="@drawable/setting_p" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="注意:改变值后,需重新合成播放"
android:textSize="18sp" />
<TextView
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:text="当前进度:0%"
android:textSize="18sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/tts_btn_person_select"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="发音人:小燕"
android:textSize="20sp" />
<Button
android:id="@+id/tts_btn_person_type"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="音频流类型:通话"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/tts_play"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="开始合成"
android:textSize="20sp" />
<Button
android:id="@+id/tts_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="取消"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/tts_pause"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="暂停播放"
android:textSize="20sp" />
<Button
android:id="@+id/tts_resume"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="继续播放"
android:textSize="20sp" />
</LinearLayout>
</LinearLayout>
③、app\src\main\res\values\strings.xml添加:
<string-array name="voicer_cloud_entries">
<item>小燕—女青、中英、普通话</item>
<item>小宇—男青、中英、普通话</item>
<item>凯瑟琳—女青、英</item>
<item>亨利—男青、英</item>
<item>玛丽—女青、英</item>
<item>小研—女青、中英、普通话</item>
<item>小琪—女青、中英、普通话</item>
<item>小峰—男青、中英、普通话</item>
<item>小梅—女青、中英、粤语</item>
<item>小莉—女青、中英、台湾普通话</item>
<item>小蓉—女青、中、四川话</item>
<item>小芸—女青、中、东北话</item>
<item>小坤—男青、中、河南话</item>
<item>小强—男青、中、湖南话</item>
<item>小莹—女青、中、陕西话</item>
<item>小新—男童、中、普通话</item>
<item>楠楠—女童、中、普通话</item>
<item>老孙—男老、中、普通话</item>
</string-array>
<string-array name="voicer_cloud_values">
<item>xiaoyan</item>
<item>xiaoyu</item>
<item>catherine</item>
<item>henry</item>
<item>vimary</item>
<item>vixy</item>
<item>xiaoqi</item>
<item>vixf</item>
<item>xiaomei</item>
<item>xiaolin</item>
<item>xiaorong</item>
<item>xiaoqian</item>
<item>xiaokun</item>
<item>xiaoqiang</item>
<item>vixying</item>
<item>xiaoxin</item>
<item>nannan</item>
<item>vils</item>
</string-array>
<string-array name="stream_entries">
<item>通话</item>
<item>系统</item>
<item>铃声</item>
<item>音乐</item>
<item>闹铃</item>
<item>通知</item>
</string-array>
<string formatted="false" name="tts_toast_format">缓冲进度为%d%%,播放进度为%d%%</string>
<string name="text_tts_source_en">iFLYTEK is a national key software enterprise dedicated to the research of intelligent speech and language technologies, development of software and chip products, provision of speech information services, and integration of E-government systems. The intelligent speech technology of iFLYTEK, the core technology of the company, represents the top level in the world.</string>
<string name="text_tts_source">科大讯飞作为中国最大的智能语音技术提供商,在智能语音技术领域有着长期的研究积累,并在中文语音合成、语音识别、口语评测等多项技术上拥有国际领先的成果。科大讯飞是我国唯一以语音技术为产业化方向的“国家863计划成果产业化基地”…</string>
关于离线合成功能貌似要收费: