Commit 1f58849f by 王涛55

集成百度语音

parent e3a8821f
......@@ -11,6 +11,9 @@
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/app_login" />
<option value="$PROJECT_DIR$/app_passport" />
<option value="$PROJECT_DIR$/buildsrc" />
<option value="$PROJECT_DIR$/lib_app_common" />
<option value="$PROJECT_DIR$/lib_baidu_audiodect" />
<option value="$PROJECT_DIR$/lib_network" />
</set>
</option>
......
......@@ -8,7 +8,7 @@ android {
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
if (build_module == build_module_app) {
applicationId rootProject.app_id
applicationId rootProject.build_modele_app_id
}
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
......@@ -40,9 +40,6 @@ android {
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
minifyEnabled false
......@@ -54,6 +51,8 @@ android {
renderscriptDebuggable false
signingConfig signingConfigs.sign
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("boolean","SHOW_LOG","false")
buildConfigField("boolean","SERVER_ADDRESS_ENABLE","false")
}
debug {
minifyEnabled false
......@@ -65,11 +64,11 @@ android {
renderscriptDebuggable true
signingConfig signingConfigs.sign
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("boolean","SHOW_LOG","true")
buildConfigField("boolean","SERVER_ADDRESS_ENABLE","true")
}
}
compileOptions {
encoding = 'utf-8'
sourceCompatibility JavaVersion.VERSION_1_8
......@@ -95,9 +94,10 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// if (build_module == build_module_app) {
// api project(':app_credit')
// }
if (build_module == build_module_app) {
api project(':lib_app_common')
api project(':app_login')
api project(':app_passport')
}
}
......@@ -4,15 +4,19 @@
package="com.module.hikcreate">
<application
android:name="com.basic.base.BasicApplication"
android:roundIcon="@mipmap/ic_launcher"
android:allowBackup="true"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:allowBackup="false"
android:theme="@android:style/Theme.NoTitleBar"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
tools:ignore="GoogleAppIndexingWarning,HardcodedDebugMode"
tools:replace="android:icon,android:roundIcon,android:theme,android:name,android:label,android:allowBackup">
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.main.testActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
......
package com.main;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import com.module.hikcreate.R;
/**
* 类说明
*
* @author wangtao55
* @date 2019/9/18
* @mail wangtao55@hikcreate.com
*/
public class testActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.app_test);
findViewById(R.id.mBtnLogin).setOnClickListener(v -> {
Intent mIntent = new Intent(testActivity.this,testLoginActivity.class);
startActivity(mIntent);
});
}
}
......@@ -11,7 +11,7 @@
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="去信用模块"
android:text="去登录模块"
android:id="@+id/mBtnLogin"
/>
......@@ -25,12 +25,6 @@
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="去预约出行模块"
android:layout_marginTop="10dp"
android:id="@+id/mBtnPlanTrip"
/>
</LinearLayout>
package com.basic.base
import android.app.Application
import com.alibaba.android.arouter.launcher.ARouter
/**
*
* author : taowang
* date :2019/9/11
* description:初始化类,用于app所有的初始化操作
*
**/
class AppContext {
companion object {
val instance: AppContext by lazy { AppContext() }
}
fun init(application: Application) {
//初始化操作
ARouter.init(application)
}
}
\ No newline at end of file
package com.basic.base
import android.app.Activity
import android.os.Bundle
import com.alibaba.android.arouter.launcher.ARouter
/**
* author : taowang
* date :2019/9/11
* description:activity 基类
**/
open class BasicActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ARouter.getInstance().inject(this)
}
}
\ No newline at end of file
package com.basic.base
import android.app.Application
/**
* author : taowang
* date :2019/9/11
* description: 基础的application类
**/
class BasicApplication :Application(){
override fun onCreate() {
super.onCreate()
AppContext.instance.init(this)
}
}
\ No newline at end of file
package com.basic.router
/**
* author : taowang
* date :2019/9/11
* description:
**/
class RouterConstants{
object AppMain {
const val MAIN_TEST = "/main/test"
}
object Credit {
const val CREDIT_TEST = "/credit/test"
}
}
\ No newline at end of file
apply plugin: 'com.android.library'
if (rootProject.build_module == build_module_login) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
defaultConfig {
if (build_module == build_module_credit) {
applicationId "com.app.login"
if (rootProject.build_module == build_module_login) {
applicationId rootProject.build_modele_login_app_id
}
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
versionCode rootProject.versionCode
versionName rootProject.versionName
resourcePrefix rootProject.build_modele_login_resourcePrefix
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
compileSdkVersion rootProject.compileSdkVersion
dexOptions {
javaMaxHeapSize "4g"
preDexLibraries = false
jumboMode = true
}
dataBinding {
enabled = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
lintOptions {
abortOnError false
}
compileOptions {
encoding = 'utf-8'
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
if (rootProject.build_module == build_module_login) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
exclude 'com/duiafudao/app_login/application/**'
}
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// implementation deps.app_compat
// testImplementation deps.junit
// androidTestImplementation deps.runner
// androidTestImplementation deps.espresso
if (build_module == build_module_app) {
api project(':lib_app_common')
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.app.login" />
xmlns:tools="http://schemas.android.com/tools"
package="com.app.login">
<application>
<activity
android:name="com.main.testLoginActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
</application>
</manifest>
\ No newline at end of file
package com.main;
import android.app.Activity;
import android.os.Bundle;
import com.app.login.R;
/**
* 类说明
*
* @author wangtao55
* @date 2019/9/18
* @mail wangtao55@hikcreate.com
*/
public class testLoginActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.lg_app_test);
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.app.login">
<application
android:roundIcon="@mipmap/lg_ic_launcher"
android:allowBackup="false"
android:theme="@android:style/Theme.NoTitleBar"
android:icon="@mipmap/lg_ic_launcher"
android:label="@string/lg_app_name_test"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.main.testLoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:padding="20dp"
android:orientation="vertical"
android:background="@android:color/white"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="我是登录"
android:id="@+id/mBtnLogin"
/>
</LinearLayout>
<resources>
<string name="app_name">lib_core</string>
<string name="lg_app_name_test">登录测试</string>
</resources>
def build_module_app = 0x00 //壳app module组件
def build_module_welcome = 0x01 // 欢迎组件
def build_module_login = 0x02 //登录组件
def build_module_mine = 0x03 //我的模块组件
def build_module_home = 0x04 //主页面组件
def build_module_credit = 0x05//信用模块组件
def build_module_passport = 0x06//通行证组件
def build_module_plan_trip = 0x07//预约出行组件
ext.build_module_app = build_module_app
ext.build_module_welcome = build_module_welcome
ext.build_module_login = build_module_login
ext.build_module_mine = build_module_mine
ext.build_module_home = build_module_home
ext.build_module_credit = build_module_credit
ext.build_module_passport = build_module_passport
ext.build_module_plan_trip = build_module_plan_trip
ext.app_id = "com.module.hikcreate"
//当前运行的model
ext.build_module = build_module_app
apply plugin: 'com.android.library'
if (rootProject.build_module == build_module_passport) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
if ( rootProject.build_module == build_module_passport) {
applicationId build_modele_login_app_id
}
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
versionCode rootProject.versionCode
versionName rootProject.versionName
resourcePrefix rootProject.build_modele_passport_resourcePrefix
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
compileSdkVersion rootProject.compileSdkVersion
buildTypes {
release {
......@@ -21,8 +30,4 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// implementation deps.app_compat
// testImplementation deps.junit
// androidTestImplementation deps.runner
// androidTestImplementation deps.espresso
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
apply from: 'app_module_config.gradle'
apply from: 'config.gradle'
apply from: 'config/app_module_config.gradle'
apply from: 'config/app_config.gradle'
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:3.1.2"
classpath 'com.android.tools.build:gradle:3.1.2'
}
}
......
apply plugin: 'groovy'
dependencies {
//gradle sdk
compile gradleApi()
//groovy sdk
compile localGroovy()
}
repositories {
jcenter()
}
package com.hikcreate.plugin
import com.hikcreate.plugin.config.PluginConfig
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
/**
* 渠道发布的plugin类
*
* @author wangtao55* @date 2019/9/4
* @mail wangtao55@hikcreate.com
*/
class ChannelPlugin implements Plugin<Project> {
final static String MAKE_CHANNEL_TASK = "makeChannel"
final static String CLEAN_PROJECT_TASK = "cleanProject"
//插件group名称
final static String MAKE_CHANNEL_GROUP = "hikcreate"
final static String MAKE_BUILD_RELEASE_TASK = "buildRelease"
final static String CLEAN_FLAG = "clean"
final static String PUBLIC_FLAG = "assembleRelease"
static def hasInit = false
//tinker 基准包路径
static def bakApkPath = PluginConfig.bakApkPath
//release包产生的路径
static def releaseApkPath = PluginConfig.releaseApkPath
//可执行的python脚本路径
static def pythonShellPath = File.separator + "buildsrc" + File.separator + "walle" + File.separator + "pack.py"
//最终tinker基准包存放的路径
static def tinkerPath = File.separator + "buildsrc" + File.separator + "walle" + File.separator + "ctinker" + File.separator
@Override
void apply(Project project) {
// project.extensions.create('channel', ChannelExtension)
System.out.println("========================")
System.out.println("start make channel package hik gradle plugin!")
System.out.println("========================")
if(!hasInit){
hasInit = true
bakApkPath = project.project.rootDir.path + bakApkPath
releaseApkPath = project.project.rootDir.path + releaseApkPath
pythonShellPath = project.project.rootDir.path + pythonShellPath
tinkerPath = project.project.rootDir.path + tinkerPath
}
makeChannelTake(project)
}
static def createFile(path, createIfNotExist) {
def file = new File(path)
if (!file.exists()) {
if (createIfNotExist) {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs()
}
file.createNewFile()
} else {
throw NullPointerException("Missing File :" + path)
}
}
return file
}
static def copyFile(fromPath, toPath, createDestIfNotExist) {
def fromFile = new File(fromPath)
def toPathFile = new File(toPath)
if (toPathFile.exists()) {
toPathFile.deleteDir()
}
toPathFile.mkdirs()
if (!fromFile.exists()) {
return false
}
def listFiles = fromFile.listFiles()
listFiles.each { file ->
if (file.isFile()) {
def toFile = createFile(toPath + "/" + file.getName(), createDestIfNotExist)
toFile.withWriter { ffile ->
file.eachLine { line ->
ffile.writeLine(line)
}
}
} else {
copyFile(file.getPath(), toPath + "/" + file.getName(), true)
}
}
}
/**
* 创建相关的task
* @param project
* @return
*/
static def makeChannelTake(Project project) {
//声明clean工程的task
Task cleanTask = project.task(CLEAN_PROJECT_TASK)
cleanTask.group = MAKE_CHANNEL_GROUP
cleanTask.dependsOn(CLEAN_FLAG)
//声明编译release版本的task
Task makeReleaseTask = project.task(MAKE_BUILD_RELEASE_TASK)
makeReleaseTask.group = MAKE_CHANNEL_GROUP
makeReleaseTask.dependsOn(PUBLIC_FLAG)
//声明makeChannelTask
Task makeChannelTask = project.task(MAKE_CHANNEL_TASK)
makeChannelTask.group = MAKE_CHANNEL_GROUP
makeChannelTask.dependsOn(makeReleaseTask)
makeChannelTask.dependsOn(cleanTask)
//noinspection UnstableApiUsage
makeReleaseTask.mustRunAfter(cleanTask)
makeChannelTask.doLast {
project.exec {
workingDir './'
commandLine "python", pythonShellPath, releaseApkPath, bakApkPath
}
}
makeChannelTask.doLast {
println 'gradle task 执行完毕...'
println '拷贝tinker备份包到对应的目录...'
try {
copyFile(bakApkPath, tinkerPath, true)
} catch (Exception e) {
e.printStackTrace()
}
}
}
}
\ No newline at end of file
package com.hikcreate.plugin.config
/**
* 不同的工程需要添加的配置,都是相对路径
*
* @author wangtao55* @date 2019/9/4
* @mail wangtao55@hikcreate.com
*/
class PluginConfig{
//build文件夹路径
static def buildPath = File.separator + "app" + File.separator + "build"+ File.separator
//tinker 基准包路径
static def bakApkPath = buildPath + "bakApk"
//release包产生的路径
static def releaseApkPath = buildPath + "outputs" + File.separator + "apk" + File.separator + "release"
}
\ No newline at end of file
package com.hikcreate.plugin.extension
/**
* 涉及到的扩展属性
*
* @author wangtao55* @date 2019/9/4
* @mail wangtao55@hikcreate.com
*/
class ChannelExtension {
String releaseApkPath //产生的release包位置,用于后续加固以及制作渠道包
String bakApkPath //产生的tinker基准包路径
String pythonShellPath//python脚步文件的路径
String tinkerPath//tinker基准包拷贝
}
\ No newline at end of file
implementation-class=com.hikcreate.plugin.ChannelPlugin
\ No newline at end of file
......@@ -13,7 +13,7 @@ ext {
compileSdkVersion = 28
// Support library
supportLibraryVersion = '28.0.0'
supportLibraryVersion = '26.1.0'
multidexVersion = "1.0.3"
guavaVersion = '22.0-android'
constraintLayout = "1.1.3"
......@@ -42,7 +42,7 @@ ext {
// rx
retrofit2Version = '2.3.0'
rxandroidVersion = '2.0.1'
rxandroidVersion = '2.0.2'
loggingInterceptor = '3.12.+'
// json tool -- to do
......
def build_module_app = 0x00 //壳app module组件
def build_module_login = 0x02 //登录组件
def build_module_passport = 0x06//通行证组件
//壳app
ext.build_module_app = build_module_app
ext.build_modele_app_id = "com.module.hikcreate"
//登录模块
ext.build_module_login = build_module_login
ext.build_modele_login_app_id = "com.app.login"
ext.build_modele_login_resourcePrefix = "lg_"
//通行证模块
ext.build_module_passport = build_module_passport
ext.build_modele_passport_app_id = "com.app.passport"
ext.build_modele_passport_resourcePrefix = "passport_"
//当前运行的model
ext.build_module = build_module_app
#Tue Dec 18 10:38:22 CST 2018
#Wed Sep 18 17:16:33 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
......
apply plugin: 'com.android.library'
android {
resourcePrefix 'app_'
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.minSdkVersion
......@@ -9,22 +8,9 @@ android {
versionName rootProject.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debugg {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api project(':lib_baidu_audiodect')
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
encoding = 'utf-8'
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
dataBinding {
enabled = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
implementation "io.reactivex.rxjava2:rxandroid:$rootProject.rxandroidVersion"
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hikcreate.baiduaudiodect"
android:versionCode="1"
android:versionName="1.0">
<!-- 集成时请添加下列权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application
android:label="@string/app_name">
<activity
android:name="com.baidu.tts.sample.AudioTestActivity"
android:label="语音合成测试" />
<activity
android:name="com.baidu.tts.sample.SynthActivity"
android:label="离在线语音合成" />
<activity
android:name="com.baidu.tts.sample.MiniActivity"
android:label="精简版合成" />
<activity
android:name="com.baidu.tts.sample.SaveFileActivity"
android:label="保存合成后的音频" />
</application>
</manifest>
\ No newline at end of file
m15
f7 Ů
yyjw ң
as ѾѾ
\ No newline at end of file
package com.baidu.tts.sample;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.hikcreate.baiduaudiodect.R;
import com.hikcreate.baiduaudiodect.SpeechSynthesizerManager;
import com.hikcreate.baiduaudiodect.listener.ISpeedStatusCallback;
import java.util.ArrayList;
/**
* SynthActivity 离在线语音合成
* MiniActivity 精简版合成
* SaveFileActivity 保存合成后的音频
*/
public class AudioTestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_test);
initButtons();
initPermission();
}
private void initButtons() {
Button b1 = (Button) findViewById(R.id.synthButton);
Button b2 = (Button) findViewById(R.id.miniButton);
Button b3 = (Button) findViewById(R.id.saveTtsFileButton);
Button b4 = (Button) findViewById(R.id.mineButton);
b1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAct(SynthActivity.class);
}
}); // 离在线语音合成
b2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAct(MiniActivity.class);
}
}); // 精简版合成
b3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAct(SaveFileActivity.class);
}
}); // 保存合成后的音频
b4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callMySpeechSynthesizer();
}
});
}
private void callMySpeechSynthesizer() {
SpeechSynthesizerManager.getInstance().speak(this, new ISpeedStatusCallback() {
@Override
public void onError(int code, String des) {
Toast.makeText(AudioTestActivity.this,des,Toast.LENGTH_LONG).show();
}
@Override
public void onStart(String utteranceId) {
Toast.makeText(AudioTestActivity.this,"start "+utteranceId,Toast.LENGTH_LONG).show();
}
@Override
public void onFinish(String utteranceId) {
Toast.makeText(AudioTestActivity.this,"finish "+utteranceId,Toast.LENGTH_LONG).show();
}
},"欢迎使用百度语音合成,请在代码中修改合成文本");
}
/**
* android 6.0 以上需要动态申请权限
*/
private void initPermission() {
String[] permissions = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.MODIFY_AUDIO_SETTINGS,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.WRITE_SETTINGS,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE
};
ArrayList<String> toApplyList = new ArrayList<String>();
for (String perm : permissions) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
toApplyList.add(perm);
// 进入到这里代表没有权限.
}
}
String[] tmpList = new String[toApplyList.size()];
if (!toApplyList.isEmpty()) {
ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
// 此处为android 6.0以上动态授权的回调,用户自行实现。
}
private void startAct(Class activityClass) {
startActivity(new Intent(this, activityClass));
}
}
\ No newline at end of file
package com.baidu.tts.sample;
import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.hikcreate.baiduaudiodect.R;
import java.util.ArrayList;
/**
* Created by fujiayi on 2017/9/13.
* <p>
* 此类 底层UI实现 无SDK相关逻辑
*/
public class BaseActivity extends AppCompatActivity implements MainHandlerConstant {
protected Button mSpeak;
protected Button mPause;
protected Button mResume;
protected Button mStop;
protected Button mSynthesize;
protected Button mBatchSpeak;
protected Button mLoadModel;
protected Button[] buttons;
protected Button mHelp;
protected EditText mInput;
protected TextView mShowText;
protected Handler mainHandler;
private static final String TAG = "MainActivity";
/*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_synth);
mainHandler = new Handler() {
/*
* @param msg
*/
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
handle(msg);
}
};
initialView(); // 初始化UI
initPermission(); // android 6.0以上动态权限申请
}
private void initialView() {
mSpeak = (Button) this.findViewById(R.id.speak);
mPause = (Button) this.findViewById(R.id.pause);
mResume = (Button) this.findViewById(R.id.resume);
mStop = (Button) this.findViewById(R.id.stop);
mSynthesize = (Button) this.findViewById(R.id.synthesize);
mBatchSpeak = (Button) this.findViewById(R.id.batchSpeak);
mLoadModel = (Button) this.findViewById(R.id.loadModel);
buttons = new Button[]{
mSpeak, mPause, mResume, mStop, mSynthesize, mBatchSpeak, mLoadModel
};
mHelp = (Button) this.findViewById(R.id.help);
mInput = (EditText) this.findViewById(R.id.input);
mShowText = (TextView) this.findViewById(R.id.showText);
mShowText.setMovementMethod(new ScrollingMovementMethod());
}
protected void handle(Message msg) {
int what = msg.what;
switch (what) {
case PRINT:
print(msg);
break;
case UI_CHANGE_INPUT_TEXT_SELECTION:
if (msg.arg1 <= mInput.getText().length()) {
mInput.setSelection(0, msg.arg1);
}
break;
case UI_CHANGE_SYNTHES_TEXT_SELECTION:
SpannableString colorfulText = new SpannableString(mInput.getText().toString());
if (msg.arg1 <= colorfulText.toString().length()) {
colorfulText.setSpan(new ForegroundColorSpan(Color.GRAY), 0, msg.arg1, Spannable
.SPAN_EXCLUSIVE_EXCLUSIVE);
mInput.setText(colorfulText);
}
break;
default:
break;
}
}
protected void toPrint(String str) {
Message msg = Message.obtain();
msg.obj = str;
mainHandler.sendMessage(msg);
}
private void print(Message msg) {
String message = (String) msg.obj;
if (message != null) {
scrollLog(message);
}
}
private void scrollLog(String message) {
Spannable colorMessage = new SpannableString(message + "\n");
colorMessage.setSpan(new ForegroundColorSpan(0xff0000ff), 0, message.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mShowText.append(colorMessage);
Layout layout = mShowText.getLayout();
if (layout != null) {
int scrollAmount = layout.getLineTop(mShowText.getLineCount()) - mShowText.getHeight();
if (scrollAmount > 0) {
mShowText.scrollTo(0, scrollAmount + mShowText.getCompoundPaddingBottom());
} else {
mShowText.scrollTo(0, 0);
}
}
}
/**
* android 6.0 以上需要动态申请权限
*/
private void initPermission() {
String[] permissions = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.MODIFY_AUDIO_SETTINGS,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.WRITE_SETTINGS,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE
};
ArrayList<String> toApplyList = new ArrayList<String>();
for (String perm : permissions) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
toApplyList.add(perm);
// 进入到这里代表没有权限.
}
}
String[] tmpList = new String[toApplyList.size()];
if (!toApplyList.isEmpty()) {
ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
// 此处为android 6.0以上动态授权的回调,用户自行实现。
}
}
package com.baidu.tts.sample;
/**
* Created by fujiayi on 2017/9/13.
*/
public interface MainHandlerConstant {
static final int PRINT = 0;
static final int UI_CHANGE_INPUT_TEXT_SELECTION = 1;
static final int UI_CHANGE_SYNTHES_TEXT_SELECTION = 2;
static final int INIT_SUCCESS = 2;
}
package com.baidu.tts.sample;
import android.Manifest;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.baidu.tts.auth.AuthInfo;
import com.baidu.tts.chainofresponsibility.logger.LoggerProxy;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.sample.control.InitConfig;
import com.baidu.tts.sample.listener.UiMessageListener;
import com.baidu.tts.sample.util.AutoCheck;
import com.hikcreate.baiduaudiodect.R;
import com.hikcreate.baiduaudiodect.model.Config;
import com.hikcreate.baiduaudiodect.model.TtsMode;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* 除了SDK,该类没有任何依赖,可以直接复制进你的项目
* <p>
* 默认TEMP_DIR = "/sdcard/baiduTTS"; // 重要!请手动将assets目录下的3个dat 文件复制到该目录
* 确保 TEXT_FILENAME 和 MODEL_FILENAME 存在
* Created by fujiayi on 2017/9/14.
*/
public class MiniActivity extends AppCompatActivity {
private static final String TEXT = "欢迎使用百度语音合成,请在代码中修改合成文本";
// ================== 初始化参数设置开始 ==========================
/**
* 发布时请替换成自己申请的appId appKey 和 secretKey。注意如果需要离线合成功能,请在您申请的应用中填写包名。
* 本demo的包名是com.baidu.tts.sample,定义在build.gradle中。
*/
// protected String appId = "11005757";
// protected String appKey = "Ovcz19MGzIKoDDb3IsFFncG1";
// protected String secretKey = "e72ebb6d43387fc7f85205ca7e6706e2";
protected String appId = Config.appId;
protected String appKey = Config.appKey;
protected String secretKey = Config.secretKey;
// TtsMode.MIX; 离在线融合,在线优先; TtsMode.ONLINE 纯在线; 没有纯离线
private TtsMode ttsMode = TtsMode.MODE_ONLINE;
// ================选择TtsMode.ONLINE 不需要设置以下参数; 选择TtsMode.MIX 需要设置下面2个离线资源文件的路径
private static final String TEMP_DIR = "/sdcard/baiduTTS"; // 重要!请手动将assets目录下的3个dat 文件复制到该目录
// 请确保该PATH下有这个文件
private static final String TEXT_FILENAME = TEMP_DIR + "/" + "bd_etts_text.dat";
// 请确保该PATH下有这个文件 ,m15是离线男声
private static final String MODEL_FILENAME =
TEMP_DIR + "/" + "bd_etts_common_speech_m15_mand_eng_high_am-mix_v3.0.0_20170505.dat";
// ===============初始化参数设置完毕,更多合成参数请至getParams()方法中设置 =================
protected SpeechSynthesizer mSpeechSynthesizer;
// =========== 以下为UI部分 ==================================================
private Button mSpeak;
private Button mStop;
private TextView mShowText;
protected Handler mainHandler;
private static final String DESC = "精简版合成,仅给出示例集成合成的调用过程。可以测试离线合成功能,首次使用请联网。\n"
+ "其中initTTS方法需要在新线程调用,否则引起UI阻塞。\n"
+ "纯在线请修改代码里ttsMode为TtsMode.ONLINE, 没有纯离线。\n"
+ "离线功能需要手动将assets目录下的资源文件复制到TEMP_DIR =/sdcard/baiduTTS \n"
+ "完整的SDK调用方式可以参见MainActivity\n\n";
private static final String TAG = "MiniActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mini);
initView();
initPermission();
initTTs();
}
/**
* 注意此处为了说明流程,故意在UI线程中调用。
* 实际集成中,该方法一定在新线程中调用,并且该线程不能结束。具体可以参考NonBlockSyntherizer的写法
*/
private void initTTs() {
LoggerProxy.printable(true); // 日志打印在logcat中
boolean isMix = ttsMode.equals(TtsMode.MODE_MIX);
boolean isSuccess;
if (isMix) {
// 检查2个离线资源是否可读
isSuccess = checkOfflineResources();
if (!isSuccess) {
return;
} else {
print("离线资源存在并且可读, 目录:" + TEMP_DIR);
}
}
// 日志更新在UI中,可以换成MessageListener,在logcat中查看日志
SpeechSynthesizerListener listener = new UiMessageListener(mainHandler);
// 1. 获取实例
mSpeechSynthesizer = SpeechSynthesizer.getInstance();
mSpeechSynthesizer.setContext(this);
// 2. 设置listener
mSpeechSynthesizer.setSpeechSynthesizerListener(listener);
// 3. 设置appId,appKey.secretKey
int result = mSpeechSynthesizer.setAppId(appId);
checkResult(result, "setAppId");
result = mSpeechSynthesizer.setApiKey(appKey, secretKey);
checkResult(result, "setApiKey");
// 4. 支持离线的话,需要设置离线模型
if (isMix) {
// 检查离线授权文件是否下载成功,离线授权文件联网时SDK自动下载管理,有效期3年,3年后的最后一个月自动更新。
isSuccess = checkAuth();
if (!isSuccess) {
return;
}
// 文本模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME);
// 声学模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME);
}
// 5. 以下setParam 参数选填。不填写则默认值生效
// 设置在线发声音人: 0 普通女声(默认) 1 普通男声 2 特别男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
// 设置合成的音量,0-9 ,默认 5
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "9");
// 设置合成的语速,0-9 ,默认 5
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "5");
// 设置合成的语调,0-9 ,默认 5
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5");
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
// 该参数设置为TtsMode.MIX生效。即纯在线模式不生效。
// MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
mSpeechSynthesizer.setAudioStreamType(AudioManager.MODE_IN_CALL);
// x. 额外 : 自动so文件是否复制正确及上面设置的参数
Map<String, String> params = new HashMap<>();
// 复制下上面的 mSpeechSynthesizer.setParam参数
// 上线时请删除AutoCheck的调用
if (isMix) {
params.put(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME);
params.put(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME);
}
InitConfig initConfig = new InitConfig(appId, appKey, secretKey, ttsMode, params, listener);
AutoCheck.getInstance(getApplicationContext()).check(initConfig, new Handler() {
@Override
/**
* 开新线程检查,成功后回调
*/
public void handleMessage(Message msg) {
if (msg.what == 100) {
AutoCheck autoCheck = (AutoCheck) msg.obj;
synchronized (autoCheck) {
String message = autoCheck.obtainDebugMessage();
print(message); // 可以用下面一行替代,在logcat中查看代码
// Log.w("AutoCheckMessage", message);
}
}
}
});
// 6. 初始化
result = mSpeechSynthesizer.initTts(ttsMode.mode);
checkResult(result, "initTts");
}
/**
* 检查appId ak sk 是否填写正确,另外检查官网应用内设置的包名是否与运行时的包名一致。本demo的包名定义在build.gradle文件中
*
* @return
*/
private boolean checkAuth() {
AuthInfo authInfo = mSpeechSynthesizer.auth(ttsMode.mode);
if (!authInfo.isSuccess()) {
// 离线授权需要网站上的应用填写包名。本demo的包名是com.baidu.tts.sample,定义在build.gradle中
String errorMsg = authInfo.getTtsError().getDetailMessage();
print("【error】鉴权失败 errorMsg=" + errorMsg);
return false;
} else {
print("验证通过,离线正式授权文件存在。");
return true;
}
}
/**
* 检查 TEXT_FILENAME, MODEL_FILENAME 这2个文件是否存在,不存在请自行从assets目录里手动复制
*
* @return
*/
private boolean checkOfflineResources() {
String[] filenames = {TEXT_FILENAME, MODEL_FILENAME};
for (String path : filenames) {
File f = new File(path);
if (!f.canRead()) {
print("[ERROR] 文件不存在或者不可读取,请从assets目录复制同名文件到:" + path);
print("[ERROR] 初始化失败!!!");
return false;
}
}
return true;
}
private void speak() {
/* 以下参数每次合成时都可以修改
* mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
* 设置在线发声音人: 0 普通女声(默认) 1 普通男声 2 特别男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
* mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "5"); 设置合成的音量,0-9 ,默认 5
* mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "5"); 设置合成的语速,0-9 ,默认 5
* mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5"); 设置合成的语调,0-9 ,默认 5
*
* mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
* MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
* MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
* MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
* MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
*/
if (mSpeechSynthesizer == null) {
print("[ERROR], 初始化失败");
return;
}
int result = mSpeechSynthesizer.speak(TEXT);
mShowText.setText("");
print("合成并播放 按钮已经点击");
checkResult(result, "speak");
}
private void stop() {
print("停止合成引擎 按钮已经点击");
int result = mSpeechSynthesizer.stop();
checkResult(result, "stop");
}
// 下面是UI部分
private void initView() {
mSpeak = (Button) this.findViewById(R.id.speak);
mStop = (Button) this.findViewById(R.id.stop);
mShowText = (TextView) this.findViewById(R.id.showText);
mShowText.setText(DESC);
View.OnClickListener listener = new View.OnClickListener() {
public void onClick(View v) {
int id = v.getId();
if (id == R.id.speak) {
speak();
} else if (id == R.id.stop) {
stop();
}
}
};
mSpeak.setOnClickListener(listener);
mStop.setOnClickListener(listener);
mainHandler = new Handler() {
/*
* @param msg
*/
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.obj != null) {
print(msg.obj.toString());
}
}
};
}
private void print(String message) {
Log.i(TAG, message);
mShowText.append(message + "\n");
}
@Override
protected void onDestroy() {
if (mSpeechSynthesizer != null) {
mSpeechSynthesizer.stop();
mSpeechSynthesizer.release();
mSpeechSynthesizer = null;
print("释放资源成功");
}
super.onDestroy();
}
private void checkResult(int result, String method) {
if (result != 0) {
print("error code :" + result + " method:" + method + ", 错误码文档:http://yuyin.baidu.com/docs/tts/122 ");
}
}
// 下面是android 6.0以上的动态授权
/**
* android 6.0 以上需要动态申请权限
*/
private void initPermission() {
String[] permissions = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.MODIFY_AUDIO_SETTINGS,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.WRITE_SETTINGS,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE
};
ArrayList<String> toApplyList = new ArrayList<String>();
for (String perm : permissions) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
toApplyList.add(perm);
// 进入到这里代表没有权限.
}
}
String[] tmpList = new String[toApplyList.size()];
if (!toApplyList.isEmpty()) {
ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
// 此处为android 6.0以上动态授权的回调,用户自行实现。
}
}
package com.baidu.tts.sample;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.sample.control.InitConfig;
import com.baidu.tts.sample.control.MySyntherizer;
import com.baidu.tts.sample.listener.FileSaveListener;
import com.baidu.tts.sample.util.FileUtil;
import java.util.Map;
/**
* 点击合成按钮,保存录音文件
* <p>
* Created by fujiayi on 2017/9/15.
*/
public class SaveFileActivity extends SynthActivity {
{
DESC = "本示例展示如何保存合成文件。 点击第一行的前三个按钮都会触发保存文件。\n"
+ "您也可以自行对用于保存文件的音频流实时实时处理。\n"
+ "其它功能请参见“语音合成”Activity。 \n";
}
/**
* 与SynthActivity相比,修改listener为FileSaveListener 可实现保存录音功能。
* 获取的音频内容同speak方法播出的声音
* FileSaveListener 在UiMessageListener的基础上,使用 onSynthesizeDataArrived回调,获取音频流
*/
protected void initialTts() {
String tmpDir = FileUtil.createTmpDir(this);
// 设置初始化参数
// 此处可以改为 含有您业务逻辑的SpeechSynthesizerListener的实现类
SpeechSynthesizerListener listener = new FileSaveListener(mainHandler, tmpDir);
Map<String, String> params = getParams();
// appId appKey secretKey 网站上您申请的应用获取。注意使用离线合成功能的话,需要应用中填写您app的包名。包名在build.gradle中获取。
InitConfig initConfig = new InitConfig(appId, appKey, secretKey, ttsMode, params, listener);
synthesizer = new MySyntherizer(this, initConfig, mainHandler); // 此处可以改为MySyntherizer 了解调用过程
}
}
/**
* Copyright (C) 2015 Baidu, Inc. All Rights Reserved.
*/
package com.baidu.tts.sample;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.baidu.tts.chainofresponsibility.logger.LoggerProxy;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.sample.control.InitConfig;
import com.baidu.tts.sample.control.MySyntherizer;
import com.baidu.tts.sample.control.NonBlockSyntherizer;
import com.baidu.tts.sample.listener.UiMessageListener;
import com.baidu.tts.sample.util.AutoCheck;
import com.baidu.tts.sample.util.OfflineResource;
import com.hikcreate.baiduaudiodect.R;
import com.hikcreate.baiduaudiodect.model.Config;
import com.hikcreate.baiduaudiodect.model.MixMode;
import com.hikcreate.baiduaudiodect.model.OfflineVoiceType;
import com.hikcreate.baiduaudiodect.model.TtsMode;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.widget.Button;
/**
* 合成demo。含在线和离线,没有纯离线功能。
* 根据网络状况优先走在线,在线时访问服务器失败后转为离线。
*/
public class SynthActivity extends BaseActivity implements View.OnClickListener {
// ================== 初始化参数设置开始 ==========================
/**
* 发布时请替换成自己申请的appId appKey 和 secretKey。注意如果需要离线合成功能,请在您申请的应用中填写包名。
* 本demo的包名是com.baidu.tts.sample,定义在build.gradle中。
*/
// protected String appId = "11005757";
// protected String appKey = "Ovcz19MGzIKoDDb3IsFFncG1";
// protected String secretKey = "e72ebb6d43387fc7f85205ca7e6706e2";
//banma
protected String appId = Config.appId;
protected String appKey = Config.appKey;
protected String secretKey = Config.secretKey;
// TtsMode.MIX; 离在线融合,在线优先; TtsMode.ONLINE 纯在线; 没有纯离线
protected TtsMode ttsMode = TtsMode.MODE_MIX;
// 离线发音选择,VOICE_FEMALE即为离线女声发音。
// assets目录下bd_etts_common_speech_m15_mand_eng_high_am-mix_v3.0.0_20170505.dat为离线男声模型;
// assets目录下bd_etts_common_speech_f7_mand_eng_high_am-mix_v3.0.0_20170512.dat为离线女声模型
protected OfflineVoiceType offlineVoice = OfflineVoiceType.COMMON_FEMALE_OFFLINE_INFORMANT;
// ===============初始化参数设置完毕,更多合成参数请至getParams()方法中设置 =================
// 主控制类,所有合成控制方法从这个类开始
protected MySyntherizer synthesizer;
protected static String DESC = "请先看完说明。之后点击“合成并播放”按钮即可正常测试。\n"
+ "测试离线合成功能需要首次联网。\n"
+ "纯在线请修改代码里ttsMode为TtsMode.ONLINE, 没有纯离线。\n"
+ "本Demo的默认参数设置为wifi情况下在线合成, 其它网络(包括4G)使用离线合成。 在线普通女声发音,离线男声发音.\n"
+ "合成可以多次调用,SDK内部有缓存队列,会依次完成。\n\n";
private static final String TAG = "SynthActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mShowText.setText(DESC);
initialButtons(); // 配置onclick
initialTts(); // 初始化TTS引擎
}
/**
* 界面上的按钮对应方法
*/
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.speak) {
speak(); // 合成并播放
} else if (id == R.id.synthesize) {
synthesize(); // 只合成不播放
} else if (id == R.id.batchSpeak) {
batchSpeak(); // 批量合成并播放
} else if (id == R.id.loadModel) {// 切换离线资源
AlertDialog.Builder builder = new AlertDialog.Builder(this, android.R.style.Theme_Holo_Light_Dialog);
builder.setTitle("引擎空闲时切换");
final Map<String, OfflineVoiceType> map = new LinkedHashMap<>(4);
map.put("离线女声", OfflineVoiceType.COMMON_FEMALE_OFFLINE_INFORMANT);
map.put("离线男声", OfflineVoiceType.COMMON_MALE_OFFLINE_INFORMANT);
map.put("离线度逍遥",OfflineVoiceType.EMOTION_DXY_OFFLINE_INFORMANT);
map.put("离线度丫丫", OfflineVoiceType.EMOTION_DYY_OFFLINE_INFORMANT);
final String[] keysTemp = new String[4];
final String[] keys = map.keySet().toArray(keysTemp);
builder.setItems(keys, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
loadModel(map.get(keys[which]));
}
});
builder.show();
} else if (id == R.id.pause) {
pause(); // 播放暂停
} else if (id == R.id.resume) {
resume(); // 播放恢复
} else if (id == R.id.stop) {
stop(); // 停止合成引擎
} else if (id == R.id.help) {
mShowText.setText(DESC);
}
}
/**
* 初始化引擎,需要的参数均在InitConfig类里
* <p>
* DEMO中提供了3个SpeechSynthesizerListener的实现
* MessageListener 仅仅用log.i记录日志,在logcat中可以看见
* UiMessageListener 在MessageListener的基础上,对handler发送消息,实现UI的文字更新
* FileSaveListener 在UiMessageListener的基础上,使用 onSynthesizeDataArrived回调,获取音频流
*/
protected void initialTts() {
LoggerProxy.printable(true); // 日志打印在logcat中
// 设置初始化参数
// 此处可以改为 含有您业务逻辑的SpeechSynthesizerListener的实现类
SpeechSynthesizerListener listener = new UiMessageListener(mainHandler);
Map<String, String> params = getParams();
// appId appKey secretKey 网站上您申请的应用获取。注意使用离线合成功能的话,需要应用中填写您app的包名。包名在build.gradle中获取。
InitConfig initConfig = new InitConfig(appId, appKey, secretKey, ttsMode, params, listener);
// 如果您集成中出错,请将下面一段代码放在和demo中相同的位置,并复制InitConfig 和 AutoCheck到您的项目中
// 上线时请删除AutoCheck的调用
AutoCheck.getInstance(getApplicationContext()).check(initConfig, new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 100) {
AutoCheck autoCheck = (AutoCheck) msg.obj;
synchronized (autoCheck) {
String message = autoCheck.obtainDebugMessage();
toPrint(message); // 可以用下面一行替代,在logcat中查看代码
// Log.w("AutoCheckMessage", message);
}
}
}
});
synthesizer = new NonBlockSyntherizer(getApplication(), initConfig, mainHandler); // 此处可以改为MySyntherizer 了解调用过程
}
/**
* 合成的参数,可以初始化时填写,也可以在合成前设置。
*
* @return
*/
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<String, String>();
// 以下参数均为选填
// 设置在线发声音人: 0 普通女声(默认) 1 普通男声 2 特别男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
params.put(SpeechSynthesizer.PARAM_SPEAKER, "0");
// 设置合成的音量,0-9 ,默认 5
params.put(SpeechSynthesizer.PARAM_VOLUME, "8");
// 设置合成的语速,0-9 ,默认 5
params.put(SpeechSynthesizer.PARAM_SPEED, "5");
// 设置合成的语调,0-9 ,默认 5
params.put(SpeechSynthesizer.PARAM_PITCH, "5");
params.put(SpeechSynthesizer.PARAM_MIX_MODE, MixMode.MIX_MODE_DEFAULT.modeName);
// 该参数设置为TtsMode.MIX生效。即纯在线模式不生效。
// MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
// 离线资源文件, 从assets目录中复制到临时目录,需要在initTTs方法前完成
OfflineResource offlineResource = createOfflineResource(offlineVoice);
// 声学模型文件路径 (离线引擎使用), 请确认下面两个文件存在
params.put(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, offlineResource.getTextFilename());
params.put(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE,
offlineResource.getModelFilename());
return params;
}
protected OfflineResource createOfflineResource(OfflineVoiceType offlineVoice) {
OfflineResource offlineResource = null;
try {
offlineResource = new OfflineResource(this, offlineVoice);
} catch (IOException e) {
// IO 错误自行处理
e.printStackTrace();
toPrint("【error】:copy files from assets failed." + e.getMessage());
}
return offlineResource;
}
/**
* speak 实际上是调用 synthesize后,获取音频流,然后播放。
* 获取音频流的方式见SaveFileActivity及FileSaveListener
* 需要合成的文本text的长度不能超过1024个GBK字节。
*/
private void speak() {
mShowText.setText("");
String text = mInput.getText().toString();
// 需要合成的文本text的长度不能超过1024个GBK字节。
if (TextUtils.isEmpty(mInput.getText())) {
text = "百度语音,面向广大开发者永久免费开放语音合成技术。";
mInput.setText(text);
}
// 合成前可以修改参数:
// Map<String, String> params = getParams();
// synthesizer.setParams(params);
int result = synthesizer.speak(text);
checkResult(result, "speak");
}
/**
* 合成但是不播放,
* 音频流保存为文件的方法可以参见SaveFileActivity及FileSaveListener
*/
private void synthesize() {
mShowText.setText("");
String text = this.mInput.getText().toString();
if (TextUtils.isEmpty(mInput.getText())) {
text = "欢迎使用百度语音合成SDK,百度语音为你提供支持。";
mInput.setText(text);
}
int result = synthesizer.synthesize(text);
checkResult(result, "synthesize");
}
/**
* 批量播放
*/
private void batchSpeak() {
mShowText.setText("");
List<Pair<String, String>> texts = new ArrayList<Pair<String, String>>();
texts.add(new Pair<String, String>("开始批量播放,", "a0"));
texts.add(new Pair<String, String>("123456,", "a1"));
texts.add(new Pair<String, String>("欢迎使用百度语音,,,", "a2"));
texts.add(new Pair<String, String>("重(chong2)量这个是多音字示例", "a3"));
int result = synthesizer.batchSpeak(texts);
checkResult(result, "batchSpeak");
}
/**
* 切换离线发音。注意需要添加额外的判断:引擎在合成时该方法不能调用
*/
private void loadModel(OfflineVoiceType mode) {
offlineVoice = mode;
OfflineResource offlineResource = createOfflineResource(offlineVoice);
toPrint("切换离线语音:" + offlineResource.getModelFilename());
int result = synthesizer.loadModel(offlineResource.getModelFilename(), offlineResource.getTextFilename());
checkResult(result, "loadModel");
}
private void checkResult(int result, String method) {
if (result != 0) {
toPrint("error code :" + result + " method:" + method + ", 错误码文档:http://yuyin.baidu.com/docs/tts/122 ");
}
}
/**
* 暂停播放。仅调用speak后生效
*/
private void pause() {
int result = synthesizer.pause();
checkResult(result, "pause");
}
/**
* 继续播放。仅调用speak后生效,调用pause生效
*/
private void resume() {
int result = synthesizer.resume();
checkResult(result, "resume");
}
/*
* 停止合成引擎。即停止播放,合成,清空内部合成队列。
*/
private void stop() {
int result = synthesizer.stop();
checkResult(result, "stop");
}
@Override
protected void onDestroy() {
synthesizer.release();
Log.i(TAG, "释放资源成功");
super.onDestroy();
}
protected void handle(Message msg) {
switch (msg.what) {
case INIT_SUCCESS:
for (Button b : buttons) {
b.setEnabled(true);
}
msg.what = PRINT;
break;
default:
break;
}
super.handle(msg);
}
private void initialButtons() {
for (Button b : buttons) {
b.setOnClickListener(this);
b.setEnabled(false); // 先禁用按钮,等待引擎初始化后打开。
}
mHelp.setOnClickListener(this);
}
}
package com.baidu.tts.sample.control;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.hikcreate.baiduaudiodect.model.TtsMode;
import java.util.Map;
/**
* 合成引擎的初始化参数
* <p>
* Created by fujiayi on 2017/9/13.
*/
public class InitConfig {
/**
* appId appKey 和 secretKey。注意如果需要离线合成功能,请在您申请的应用中填写包名。
* 本demo的包名是com.baidu.tts.sample,定义在build.gradle中。
*/
private String appId;
private String appKey;
private String secretKey;
/**
* 纯在线或者离在线融合
*/
private TtsMode ttsMode;
/**
* 初始化的其它参数,用于setParam
*/
private Map<String, String> params;
/**
* 合成引擎的回调
*/
private SpeechSynthesizerListener listener;
private InitConfig() {
}
public InitConfig(String appId, String appKey, String secretKey, TtsMode ttsMode,
Map<String, String> params, SpeechSynthesizerListener listener) {
this.appId = appId;
this.appKey = appKey;
this.secretKey = secretKey;
this.ttsMode = ttsMode;
this.params = params;
this.listener = listener;
}
public SpeechSynthesizerListener getListener() {
return listener;
}
public Map<String, String> getParams() {
return params;
}
public String getAppId() {
return appId;
}
public String getAppKey() {
return appKey;
}
public String getSecretKey() {
return secretKey;
}
public TtsMode getTtsMode() {
return ttsMode;
}
}
package com.baidu.tts.sample.control;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.Pair;
import com.baidu.tts.auth.AuthInfo;
import com.baidu.tts.client.SpeechSynthesizeBag;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.TtsMode;
import com.baidu.tts.sample.MainHandlerConstant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 该类是对SpeechSynthesizer的封装
* <p>
* Created by fujiayi on 2017/5/24.
*/
public class MySyntherizer implements MainHandlerConstant {
protected SpeechSynthesizer mSpeechSynthesizer;
protected Context context;
protected Handler mainHandler;
private static final String TAG = "NonBlockSyntherizer";
private static boolean isInitied = false;
private boolean isCheckFile = true;
public MySyntherizer(Context context, InitConfig initConfig, Handler mainHandler) {
this(context, mainHandler);
init(initConfig);
}
protected MySyntherizer(Context context, Handler mainHandler) {
if (isInitied) {
// SpeechSynthesizer.getInstance() 不要连续调用
throw new RuntimeException("MySynthesizer 类里面 SpeechSynthesizer还未释放,请勿新建一个新类");
}
this.context = context;
this.mainHandler = mainHandler;
isInitied = true;
}
/**
* 注意该方法需要在新线程中调用。且该线程不能结束。详细请参见NonBlockSyntherizer的实现
*
* @param config
* @return
*/
protected boolean init(InitConfig config) {
sendToUiThread("初始化开始");
boolean isMix = config.getTtsMode().equals(TtsMode.MIX);
mSpeechSynthesizer = SpeechSynthesizer.getInstance();
mSpeechSynthesizer.setContext(context);
mSpeechSynthesizer.setSpeechSynthesizerListener(config.getListener());
// 请替换为语音开发者平台上注册应用得到的App ID ,AppKey ,Secret Key ,填写在SynthActivity的开始位置
mSpeechSynthesizer.setAppId(config.getAppId());
mSpeechSynthesizer.setApiKey(config.getAppKey(), config.getSecretKey());
if (isMix) {
// 授权检测接口(只是通过AuthInfo进行检验授权是否成功。选择纯在线可以不必调用auth方法。
AuthInfo authInfo = mSpeechSynthesizer.auth(config.getTtsMode().mode);
if (!authInfo.isSuccess()) {
// 离线授权需要网站上的应用填写包名。本demo的包名是com.baidu.tts.sample,定义在build.gradle中
String errorMsg = authInfo.getTtsError().getDetailMessage();
sendToUiThread("鉴权失败 =" + errorMsg);
return false;
} else {
sendToUiThread("验证通过,离线正式授权文件存在。");
}
}
setParams(config.getParams());
// 初始化tts
int result = mSpeechSynthesizer.initTts(config.getTtsMode().mode);
if (result != 0) {
sendToUiThread("【error】initTts 初始化失败 + errorCode:" + result);
return false;
}
// 此时可以调用 speak和synthesize方法
sendToUiThread(INIT_SUCCESS, "合成引擎初始化成功");
return true;
}
/**
* 合成并播放
*
* @param text 小于1024 GBK字节,即512个汉字或者字母数字
* @return
*/
public int speak(String text) {
Log.i(TAG, "speak text:" + text);
return mSpeechSynthesizer.speak(text);
}
/**
* 合成并播放
*
* @param text 小于1024 GBK字节,即512个汉字或者字母数字
* @param utteranceId 用于listener的回调,默认"0"
* @return
*/
public int speak(String text, String utteranceId) {
return mSpeechSynthesizer.speak(text, utteranceId);
}
/**
* 只合成不播放
*
* @param text
* @return
*/
public int synthesize(String text) {
return mSpeechSynthesizer.synthesize(text);
}
public int synthesize(String text, String utteranceId) {
return mSpeechSynthesizer.synthesize(text, utteranceId);
}
public int batchSpeak(List<Pair<String, String>> texts) {
List<SpeechSynthesizeBag> bags = new ArrayList<SpeechSynthesizeBag>();
for (Pair<String, String> pair : texts) {
SpeechSynthesizeBag speechSynthesizeBag = new SpeechSynthesizeBag();
speechSynthesizeBag.setText(pair.first);
if (pair.second != null) {
speechSynthesizeBag.setUtteranceId(pair.second);
}
bags.add(speechSynthesizeBag);
}
return mSpeechSynthesizer.batchSpeak(bags);
}
public void setParams(Map<String, String> params) {
if (params != null) {
for (Map.Entry<String, String> e : params.entrySet()) {
mSpeechSynthesizer.setParam(e.getKey(), e.getValue());
}
}
}
public int pause() {
return mSpeechSynthesizer.pause();
}
public int resume() {
return mSpeechSynthesizer.resume();
}
public int stop() {
return mSpeechSynthesizer.stop();
}
/**
* 引擎在合成时该方法不能调用!!!
* 注意 只有 TtsMode.MIX 才可以切换离线发音
*
* @return
*/
public int loadModel(String modelFilename, String textFilename) {
int res = mSpeechSynthesizer.loadModel(modelFilename, textFilename);
sendToUiThread("切换离线发音人成功。");
return res;
}
/**
* 设置播放音量,默认已经是最大声音
* 0.0f为最小音量,1.0f为最大音量
*
* @param leftVolume [0-1] 默认1.0f
* @param rightVolume [0-1] 默认1.0f
*/
public void setStereoVolume(float leftVolume, float rightVolume) {
mSpeechSynthesizer.setStereoVolume(leftVolume, rightVolume);
}
public void release() {
mSpeechSynthesizer.stop();
mSpeechSynthesizer.release();
mSpeechSynthesizer = null;
isInitied = false;
}
protected void sendToUiThread(String message) {
sendToUiThread(PRINT, message);
}
protected void sendToUiThread(int action, String message) {
Log.i(TAG, message);
if (mainHandler == null) { // 可以不依赖mainHandler
return;
}
Message msg = Message.obtain();
msg.what = action;
msg.obj = message + "\n";
mainHandler.sendMessage(msg);
}
}
package com.baidu.tts.sample.control;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
/**
* 在新线程中调用initTTs方法。防止UI柱塞
* <p>
* Created by fujiayi on 2017/5/24.
*/
public class NonBlockSyntherizer extends MySyntherizer {
private static final int INIT = 1;
private static final int RELEASE = 11;
private HandlerThread hThread;
private Handler tHandler;
private static final String TAG = "NonBlockSyntherizer";
public NonBlockSyntherizer(Context context, InitConfig initConfig, Handler mainHandler) {
super(context, mainHandler);
initThread();
runInHandlerThread(INIT, initConfig);
}
protected void initThread() {
hThread = new HandlerThread("NonBlockSyntherizer-thread");
hThread.start();
tHandler = new Handler(hThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case INIT:
InitConfig config = (InitConfig) msg.obj;
boolean isSuccess = init(config);
if (isSuccess) {
// speak("初始化成功");
sendToUiThread("NonBlockSyntherizer 初始化成功");
} else {
sendToUiThread("合成引擎初始化失败, 请查看日志");
}
break;
case RELEASE:
NonBlockSyntherizer.super.release();
if (Build.VERSION.SDK_INT < 18) {
getLooper().quit();
}
break;
default:
break;
}
}
};
}
@Override
public void release() {
runInHandlerThread(RELEASE);
if (Build.VERSION.SDK_INT >= 18) {
hThread.quitSafely();
}
}
private void runInHandlerThread(int action) {
runInHandlerThread(action, null);
}
private void runInHandlerThread(int action, Object obj) {
Message msg = Message.obtain();
msg.what = action;
msg.obj = obj;
tHandler.sendMessage(msg);
}
}
package com.baidu.tts.sample.listener;
import android.os.Handler;
import android.util.Log;
import com.baidu.tts.client.SpeechError;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 保存回调音频流到文件。您也可以直接处理音频流
* FileSaveListener 在UiMessageListener的基础上,使用 onSynthesizeDataArrived回调,获取音频流
* Created by fujiayi on 2017/9/15.
*/
public class FileSaveListener extends UiMessageListener {
/**
* 保存的文件名 baseName + utteranceId, 通常是 output-0.pcm
*/
private String baseName = "output-";
/**
* 保存文件的目录
*/
private String destDir;
/**
* 文件
*/
private File ttsFile;
/**
* ttsFile 文件流
*/
private FileOutputStream ttsFileOutputStream;
/**
* ttsFile 文件buffer流
*/
private BufferedOutputStream ttsFileBufferedOutputStream;
private static final String TAG = "FileSaveListener";
public FileSaveListener(Handler mainHandler, String destDir) {
super(mainHandler);
this.destDir = destDir;
}
@Override
public void onSynthesizeStart(String utteranceId) {
String filename = baseName + utteranceId + ".pcm";
// 保存的语音文件是 16K采样率 16bits编码 单声道 pcm文件。
ttsFile = new File(destDir, filename);
Log.i(TAG, "try to write audio file to " + ttsFile.getAbsolutePath());
try {
if (ttsFile.exists()) {
ttsFile.delete();
}
ttsFile.createNewFile();
// 创建FileOutputStream对象
FileOutputStream ttsFileOutputStream = new FileOutputStream(ttsFile);
// 创建BufferedOutputStream对象
ttsFileBufferedOutputStream = new BufferedOutputStream(ttsFileOutputStream);
} catch (IOException e) {
// 请自行做错误处理
e.printStackTrace();
sendMessage("创建文件失败:" + destDir + "/" + filename);
throw new RuntimeException(e);
}
sendMessage("创建文件成功:" + destDir + "/" + filename);
}
/**
* 语音流 16K采样率 16bits编码 单声道 。
*
* @param utteranceId
* @param data 二进制语音 ,注意可能有空data的情况,可以忽略
* @param progress 如合成“百度语音问题”这6个字, progress肯定是从0开始,到6结束。 但progress无法保证和合成到第几个字对应。
*/
@Override
public void onSynthesizeDataArrived(String utteranceId, byte[] data, int progress) {
super.onSynthesizeDataArrived(utteranceId, data, progress);
Log.i(TAG, "合成进度回调, progress:" + progress + ";序列号:" + utteranceId);
try {
ttsFileBufferedOutputStream.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onSynthesizeFinish(String utteranceId) {
super.onSynthesizeFinish(utteranceId);
close();
}
/**
* 当合成或者播放过程中出错时回调此接口
*
* @param utteranceId
* @param speechError 包含错误码和错误信息
*/
@Override
public void onError(String utteranceId, SpeechError speechError) {
close();
super.onError(utteranceId, speechError);
}
/**
* 关闭流,注意可能stop导致该方法没有被调用
*/
private void close() {
if (ttsFileBufferedOutputStream != null) {
try {
ttsFileBufferedOutputStream.flush();
ttsFileBufferedOutputStream.close();
ttsFileBufferedOutputStream = null;
} catch (Exception e2) {
e2.printStackTrace();
}
}
if (ttsFileOutputStream != null) {
try {
ttsFileOutputStream.close();
ttsFileOutputStream = null;
} catch (IOException e) {
e.printStackTrace();
}
}
sendMessage("关闭文件成功");
}
}
package com.baidu.tts.sample.listener;
import android.util.Log;
import com.baidu.tts.client.SpeechError;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.sample.MainHandlerConstant;
/**
* SpeechSynthesizerListener 简单地实现,仅仅记录日志
* Created by fujiayi on 2017/5/19.
*/
public class MessageListener implements SpeechSynthesizerListener, MainHandlerConstant {
private static final String TAG = "MessageListener";
/**
* 播放开始,每句播放开始都会回调
*
* @param utteranceId
*/
@Override
public void onSynthesizeStart(String utteranceId) {
sendMessage("准备开始合成,序列号:" + utteranceId);
}
/**
* 语音流 16K采样率 16bits编码 单声道 。
*
* @param utteranceId
* @param bytes 二进制语音 ,注意可能有空data的情况,可以忽略
* @param progress 如合成“百度语音问题”这6个字, progress肯定是从0开始,到6结束。 但progress无法和合成到第几个字对应。
*/
@Override
public void onSynthesizeDataArrived(String utteranceId, byte[] bytes, int progress) {
// Log.i(TAG, "合成进度回调, progress:" + progress + ";序列号:" + utteranceId );
}
/**
* 合成正常结束,每句合成正常结束都会回调,如果过程中出错,则回调onError,不再回调此接口
*
* @param utteranceId
*/
@Override
public void onSynthesizeFinish(String utteranceId) {
sendMessage("合成结束回调, 序列号:" + utteranceId);
}
@Override
public void onSpeechStart(String utteranceId) {
sendMessage("播放开始回调, 序列号:" + utteranceId);
}
/**
* 播放进度回调接口,分多次回调
*
* @param utteranceId
* @param progress 如合成“百度语音问题”这6个字, progress肯定是从0开始,到6结束。 但progress无法保证和合成到第几个字对应。
*/
@Override
public void onSpeechProgressChanged(String utteranceId, int progress) {
// Log.i(TAG, "播放进度回调, progress:" + progress + ";序列号:" + utteranceId );
}
/**
* 播放正常结束,每句播放正常结束都会回调,如果过程中出错,则回调onError,不再回调此接口
*
* @param utteranceId
*/
@Override
public void onSpeechFinish(String utteranceId) {
sendMessage("播放结束回调, 序列号:" + utteranceId);
}
/**
* 当合成或者播放过程中出错时回调此接口
*
* @param utteranceId
* @param speechError 包含错误码和错误信息
*/
@Override
public void onError(String utteranceId, SpeechError speechError) {
sendErrorMessage("错误发生:" + speechError.description + ",错误编码:"
+ speechError.code + ",序列号:" + utteranceId);
}
private void sendErrorMessage(String message) {
sendMessage(message, true);
}
private void sendMessage(String message) {
sendMessage(message, false);
}
protected void sendMessage(String message, boolean isError) {
if (isError) {
Log.e(TAG, message);
} else {
Log.i(TAG, message);
}
}
}
package com.baidu.tts.sample.listener;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* 在 MessageListener的基础上,和UI配合。
* Created by fujiayi on 2017/9/14.
*/
public class UiMessageListener extends MessageListener {
private Handler mainHandler;
private static final String TAG = "UiMessageListener";
public UiMessageListener(Handler mainHandler) {
super();
this.mainHandler = mainHandler;
}
/**
* 合成数据和进度的回调接口,分多次回调。
* 注意:progress表示进度,与播放到哪个字无关
* @param utteranceId
* @param data 合成的音频数据。该音频数据是采样率为16K,2字节精度,单声道的pcm数据。
* @param progress 文本按字符划分的进度,比如:你好啊 进度是0-3
*/
@Override
public void onSynthesizeDataArrived(String utteranceId, byte[] data, int progress) {
// sendMessage("onSynthesizeDataArrived");
mainHandler.sendMessage(mainHandler.obtainMessage(UI_CHANGE_SYNTHES_TEXT_SELECTION, progress, 0));
}
/**
* 播放进度回调接口,分多次回调
* 注意:progress表示进度,与播放到哪个字无关
*
* @param utteranceId
* @param progress 文本按字符划分的进度,比如:你好啊 进度是0-3
*/
@Override
public void onSpeechProgressChanged(String utteranceId, int progress) {
// sendMessage("onSpeechProgressChanged");
mainHandler.sendMessage(mainHandler.obtainMessage(UI_CHANGE_INPUT_TEXT_SELECTION, progress, 0));
}
protected void sendMessage(String message) {
sendMessage(message, false);
}
@Override
protected void sendMessage(String message, boolean isError) {
sendMessage(message, isError, PRINT);
}
protected void sendMessage(String message, boolean isError, int action) {
super.sendMessage(message, isError);
if (mainHandler != null) {
Message msg = Message.obtain();
msg.what = action;
msg.obj = message + "\n";
mainHandler.sendMessage(msg);
Log.i(TAG, message);
}
}
}
package com.baidu.tts.sample.util;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Message;
import android.support.v4.content.ContextCompat;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.TtsMode;
import com.baidu.tts.sample.control.InitConfig;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeSet;
/**
* Created by fujiayi on 2017/12/28.
*/
/**
* 自动排查工具,用于集成后发现错误。
* <p>
* 可以检测如下错误:
* 1. PermissionCheck : AndroidManifest,xml 需要的部分权限
* 2. JniCheck: 检测so文件是否安装在指定目录
* 3. AppInfoCheck: 联网情况下 , 检测appId appKey secretKey是否正确
* 4. ApplicationIdCheck: 显示包名applicationId, 提示用户手动去官网检查
* 5. ParamKeyExistCheck: 检查key是否存在,目前检查 SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE
* 和PARAM_TTS_SPEECH_MODEL_FILE
* 6. OfflineResourceFileCheck 检查离线资源文件(需要从assets目录下复制),是否存在
* <p>
* <p>
* 示例使用代码:
* AutoCheck.getInstance(getApplicationContext()).check(initConfig, new Handler() {
*
* @Override public void handleMessage(Message msg) {
* if (msg.what == 100) {
* AutoCheck autoCheck = (AutoCheck) msg.obj;
* synchronized (autoCheck) {
* String message = autoCheck.obtainDebugMessage();
* toPrint(message); // 可以用下面一行替代,在logcat中查看代码
* //Log.w("AutoCheckMessage",message);
* }
* }
* }
* <p>
* });
*/
public class AutoCheck {
private static AutoCheck instance;
private LinkedHashMap<String, Check> checks;
private static Context context;
private boolean hasError = false;
volatile boolean isFinished = false;
/**
* 获取实例,非线程安全
*
* @return
*/
public static AutoCheck getInstance(Context context) {
if (instance == null || AutoCheck.context != context) {
instance = new AutoCheck(context);
}
return instance;
}
public void check(final InitConfig initConfig, final Handler handler) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
AutoCheck obj = innerCheck(initConfig);
isFinished = true;
synchronized (obj) { // 偶发,同步线程信息
Message msg = handler.obtainMessage(100, obj);
handler.sendMessage(msg);
}
}
});
t.start();
}
private AutoCheck innerCheck(InitConfig config) {
checks.put("检查申请的Android权限", new PermissionCheck(context));
checks.put("检查4个so文件是否存在", new JniCheck(context));
checks.put("检查AppId AppKey SecretKey",
new AppInfoCheck(config.getAppId(), config.getAppKey(), config.getSecretKey()));
checks.put("检查包名", new ApplicationIdCheck(context, config.getAppId()));
if (TtsMode.MIX.equals(config.getTtsMode())) {
Map<String, String> params = config.getParams();
String fileKey = SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE;
checks.put("检查离线资TEXT文件参数", new ParamKeyExistCheck(params, fileKey,
"SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE未设置 ,"));
checks.put("检查离线资源TEXT文件", new OfflineResourceFileCheck(params.get(fileKey)));
fileKey = SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE;
checks.put("检查离线资Speech文件参数", new ParamKeyExistCheck(params, fileKey,
"SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE未设置 ,"));
checks.put("检查离线资源Speech文件", new OfflineResourceFileCheck(params.get(fileKey)));
}
for (Map.Entry<String, Check> e : checks.entrySet()) {
Check check = e.getValue();
check.check();
if (check.hasError()) {
break;
}
}
return this;
}
public String obtainErrorMessage() {
PrintConfig config = new PrintConfig();
return formatString(config);
}
public String obtainDebugMessage() {
PrintConfig config = new PrintConfig();
config.withInfo = true;
return formatString(config);
}
public String obtainAllMessage() {
PrintConfig config = new PrintConfig();
config.withLog = true;
config.withInfo = true;
return formatString(config);
}
public String formatString(PrintConfig config) {
StringBuilder sb = new StringBuilder();
hasError = false;
for (HashMap.Entry<String, Check> entry : checks.entrySet()) {
Check check = entry.getValue();
String testName = entry.getKey();
if (check.hasError()) {
if (!hasError) {
hasError = true;
}
sb.append("【错误】【").append(testName).append(" 】 ").append(check.getErrorMessage()).append("\n");
if (check.hasFix()) {
sb.append("【修复方法】【").append(testName).append(" 】 ").append(check.getFixMessage()).append("\n");
}
}
if (config.withInfo && check.hasInfo()) {
sb.append("【请手动检查】【").append(testName).append("】 ").append(check.getInfoMessage()).append("\n");
}
if (config.withLog && (config.withLogOnSuccess || hasError) && check.hasLog()) {
sb.append("【log】:" + check.getLogMessage()).append("\n");
}
}
if (!hasError) {
sb.append("集成自动排查工具: 恭喜没有检测到任何问题\n");
}
return sb.toString();
}
public void clear() {
checks.clear();
hasError = false;
}
private AutoCheck(Context context) {
this.context = context;
checks = new LinkedHashMap<String, Check>();
}
private static class PrintConfig {
public boolean withFix = true;
public boolean withInfo = false;
public boolean withLog = false;
public boolean withLogOnSuccess = false;
}
private static class PermissionCheck extends Check {
private Context context;
public PermissionCheck(Context context) {
this.context = context;
}
@Override
public void check() {
String[] permissions = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.MODIFY_AUDIO_SETTINGS,
// Manifest.permission.WRITE_EXTERNAL_STORAGE,
// Manifest.permission.WRITE_SETTINGS,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.ACCESS_WIFI_STATE,
// Manifest.permission.CHANGE_WIFI_STATE
};
ArrayList<String> toApplyList = new ArrayList<String>();
for (String perm : permissions) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(context, perm)) {
toApplyList.add(perm);
// 进入到这里代表没有权限.
}
}
if (!toApplyList.isEmpty()) {
errorMessage = "缺少权限:" + toApplyList;
fixMessage = "请从AndroidManifest.xml复制相关权限";
}
}
}
private static class JniCheck extends Check {
private Context context;
private String[] soNames;
public JniCheck(Context context) {
this.context = context;
soNames = new String[]{"libbd_etts.so", "libBDSpeechDecoder_V1.so", "libbdtts.so", "libgnustl_shared.so"};
}
@Override
public void check() {
String path = context.getApplicationInfo().nativeLibraryDir;
appendLogMessage("Jni so文件目录 " + path);
File[] files = new File(path).listFiles();
TreeSet<String> set = new TreeSet<>();
if (files != null) {
for (File file : files) {
if (file.canRead()) {
set.add(file.getName());
}
}
}
appendLogMessage("Jni目录内文件: " + set.toString());
for (String name : soNames) {
if (!set.contains(name)) {
errorMessage = "Jni目录" + path + " 缺少可读的so文件:" + name + ", 该目录文件列表: " + set.toString();
fixMessage = "如果您的app内没有其它so文件,请复制demo里的src/main/jniLibs至同名目录。"
+ " 如果app内有so文件,请合并目录放一起(注意目录取交集,多余的目录删除)。";
break;
}
}
}
}
private static class ParamKeyExistCheck extends Check {
private Map<String, String> params;
private String key;
private String prefixErrorMessage;
public ParamKeyExistCheck(Map<String, String> params, String key, String prefixErrorMessage) {
this.params = params;
this.key = key;
this.prefixErrorMessage = prefixErrorMessage;
}
@Override
public void check() {
if (params == null || !params.containsKey(key)) {
errorMessage = prefixErrorMessage + " 参数中没有设置:" + key;
fixMessage = "请参照demo在设置 " + key + "参数";
}
}
}
private static class OfflineResourceFileCheck extends Check {
private String filename;
private String nullMessage;
public OfflineResourceFileCheck(String filename) {
this.filename = filename;
this.nullMessage = nullMessage;
}
@Override
public void check() {
File file = new File(filename);
boolean isSuccess = true;
if (!file.exists()) {
errorMessage = "资源文件不存在:" + filename;
isSuccess = false;
} else if (!file.canRead()) {
errorMessage = "资源文件不可读:" + filename;
isSuccess = false;
}
if (!isSuccess) {
fixMessage = "请将demo中src/main/assets目录下同名文件复制到 " + filename;
}
}
}
private static class ApplicationIdCheck extends Check {
private String appId;
private Context context;
public ApplicationIdCheck(Context context, String appId) {
this.appId = appId;
this.context = context;
}
@Override
public void check() {
infoMessage = "如果您集成过程中遇见离线合成初始化问题,请检查网页上appId:" + appId
+ " 应用是否开通了合成服务,并且网页上的应用填写了Android包名:"
+ getApplicationId();
}
private String getApplicationId() {
return context.getPackageName();
}
}
private static class AppInfoCheck extends Check {
private String appId;
private String appKey;
private String secretKey;
public AppInfoCheck(String appId, String appKey, String secretKey) {
this.appId = appId;
this.appKey = appKey;
this.secretKey = secretKey;
}
public void check() {
do {
appendLogMessage("try to check appId " + appId + " ,appKey=" + appKey + " ,secretKey" + secretKey);
if (appId == null || appId.isEmpty()) {
errorMessage = "appId 为空";
fixMessage = "填写appID";
break;
}
if (appKey == null || appKey.isEmpty()) {
errorMessage = "appKey 为空";
fixMessage = "填写appID";
break;
}
if (secretKey == null || secretKey.isEmpty()) {
errorMessage = "secretKey 为空";
fixMessage = "secretKey";
break;
}
} while (false);
try {
checkOnline();
} catch (UnknownHostException e) {
infoMessage = "无网络或者网络不连通,忽略检测 : " + e.getMessage();
} catch (Exception e) {
errorMessage = e.getClass().getCanonicalName() + ":" + e.getMessage();
fixMessage = " 重新检测appId, appKey, appSecret是否正确";
}
}
public void checkOnline() throws Exception {
String urlpath = "http://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id="
+ appKey + "&client_secret=" + secretKey;
URL url = new URL(urlpath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(1000);
InputStream is = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder result = new StringBuilder();
String line = "";
do {
line = reader.readLine();
if (line != null) {
result.append(line);
}
} while (line != null);
String res = result.toString();
appendLogMessage("openapi return " + res);
JSONObject jsonObject = new JSONObject(res);
String error = jsonObject.optString("error");
if (error != null && !error.isEmpty()) {
throw new Exception("appkey secretKey 错误" + ", error:" + error + ", json is" + result);
}
String token = jsonObject.getString("access_token");
if (token == null || !token.endsWith("-" + appId)) {
throw new Exception("appId 与 appkey及 appSecret 不一致。appId = " + appId + " ,token = " + token);
}
}
}
private abstract static class Check {
protected String errorMessage = null;
protected String fixMessage = null;
protected String infoMessage = null;
protected StringBuilder logMessage;
public Check() {
logMessage = new StringBuilder();
}
public abstract void check();
public boolean hasError() {
return errorMessage != null;
}
public boolean hasFix() {
return fixMessage != null;
}
public boolean hasInfo() {
return infoMessage != null;
}
public boolean hasLog() {
return !logMessage.toString().isEmpty();
}
public void appendLogMessage(String message) {
logMessage.append(message + "\n");
}
public String getErrorMessage() {
return errorMessage;
}
public String getFixMessage() {
return fixMessage;
}
public String getInfoMessage() {
return infoMessage;
}
public String getLogMessage() {
return logMessage.toString();
}
}
}
package com.baidu.tts.sample.util;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by fujiayi on 2017/5/19.
*/
public class FileUtil {
private static final String sAudioDir = "baiduTTS";
// 创建一个临时目录,用于复制临时文件,如assets目录下的离线资源文件
public static String createTmpDir(Context context) {
String tmpDir = Environment.getExternalStorageDirectory().toString() + "/" + sAudioDir;
if (!FileUtil.makeDir(tmpDir)) {
tmpDir = context.getExternalFilesDir(sAudioDir).getAbsolutePath();
if (!FileUtil.makeDir(sAudioDir)) {
throw new RuntimeException("create model resources dir failed :" + tmpDir);
}
}
return tmpDir;
}
public static boolean fileCanRead(String filename) {
File f = new File(filename);
return f.canRead();
}
public static boolean makeDir(String dirPath) {
File file = new File(dirPath);
if (!file.exists()) {
return file.mkdirs();
} else {
return true;
}
}
public static void copyFromAssets(AssetManager assets, String source, String dest, boolean isCover)
throws IOException {
File file = new File(dest);
if (isCover || (!isCover && !file.exists())) {
InputStream is = null;
FileOutputStream fos = null;
try {
is = assets.open(source);
String path = dest;
fos = new FileOutputStream(path);
byte[] buffer = new byte[1024];
int size = 0;
while ((size = is.read(buffer, 0, 1024)) >= 0) {
fos.write(buffer, 0, size);
}
} finally {
if (fos != null) {
try {
fos.close();
} finally {
if (is != null) {
is.close();
}
}
}
}
}
}
}
package com.baidu.tts.sample.util;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;
import com.hikcreate.baiduaudiodect.model.OfflineVoiceType;
import java.io.IOException;
import java.util.HashMap;
import static android.content.ContentValues.TAG;
/**
* Created by fujiayi on 2017/5/19.
*/
public class OfflineResource {
private static final String SAMPLE_DIR = "baiduTTS";
private AssetManager assets;
private String destPath;
private String textFilename;
private String modelFilename;
private static HashMap<String, Boolean> mapInitied = new HashMap<String, Boolean>();
public OfflineResource(Context context, OfflineVoiceType offlineVoiceType) throws IOException {
context = context.getApplicationContext();
this.assets = context.getApplicationContext().getAssets();
this.destPath = FileUtil.createTmpDir(context);
setOfflineVoiceType(offlineVoiceType);
}
public String getModelFilename() {
return modelFilename;
}
public String getTextFilename() {
return textFilename;
}
public void setOfflineVoiceType(OfflineVoiceType offlineVoiceType) throws IOException {
String text = "bd_etts_text.dat";
String model;
switch (offlineVoiceType){
case COMMON_MALE_OFFLINE_INFORMANT:
model = "bd_etts_common_speech_m15_mand_eng_high_am-mix_v3.0.0_20170505.dat";
break;
case COMMON_FEMALE_OFFLINE_INFORMANT:
model = "bd_etts_common_speech_f7_mand_eng_high_am-mix_v3.0.0_20170512.dat";
break;
case EMOTION_DXY_OFFLINE_INFORMANT:
model = "bd_etts_common_speech_yyjw_mand_eng_high_am-mix_v3.0.0_20170512.dat";
break;
case EMOTION_DYY_OFFLINE_INFORMANT:
model = "bd_etts_common_speech_yyjw_mand_eng_high_am-mix_v3.0.0_20170512.dat";
break;
default:
throw new RuntimeException("voice type is not in list");
}
textFilename = copyAssetsFile(text);
modelFilename = copyAssetsFile(model);
}
private String copyAssetsFile(String sourceFilename) throws IOException {
String destFilename = destPath + "/" + sourceFilename;
boolean recover = false;
Boolean existed = mapInitied.get(sourceFilename); // 启动时完全覆盖一次
if (existed == null || !existed) {
recover = true;
}
FileUtil.copyFromAssets(assets, sourceFilename, destFilename, recover);
Log.i(TAG, "文件复制成功:" + destFilename);
return destFilename;
}
}
package com.hikcreate.baiduaudiodect;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.util.Pair;
import com.baidu.tts.auth.AuthInfo;
import com.baidu.tts.client.SpeechError;
import com.baidu.tts.client.SpeechSynthesizeBag;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.sample.util.OfflineResource;
import com.hikcreate.baiduaudiodect.listener.ASpeechSynthesizerManager;
import com.hikcreate.baiduaudiodect.listener.IInitCallback;
import com.hikcreate.baiduaudiodect.listener.ISpeedFocusCallback;
import com.hikcreate.baiduaudiodect.listener.ISpeedInitStatusCallback;
import com.hikcreate.baiduaudiodect.listener.ISpeedStatusCallback;
import com.hikcreate.baiduaudiodect.listener.SpeechSynthesizerAdapter;
import com.hikcreate.baiduaudiodect.model.Config;
import com.hikcreate.baiduaudiodect.model.Informant;
import com.hikcreate.baiduaudiodect.model.MixMode;
import com.hikcreate.baiduaudiodect.model.OfflineVoiceType;
import com.hikcreate.baiduaudiodect.model.TtsMode;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
/**
* @author yslei
* @date 2019/6/14
* @email leiyongsheng@hikcreate.com
*/
public class SpeechSynthesizerManager extends ASpeechSynthesizerManager {
private static SpeechSynthesizerManager sInstance = null;
public static synchronized SpeechSynthesizerManager getInstance() {
if (sInstance == null) {
sInstance = new SpeechSynthesizerManager();
}
return sInstance;
}
private Context mApplicationContext;
private AudioManager mAudioManager;
private AudioFocuseChangeCallback mAudioFocusChangeListener;
private MainHandler mMainHandler;
private SpeechSynthesizer mSpeechSynthesizer;
private boolean mIsInitSuccess;
private Informant mInformant = Informant.COMMON_FEMALE_INFORMANT;//发音人
private OfflineVoiceType mOfflineVoiceType = OfflineVoiceType.COMMON_FEMALE_OFFLINE_INFORMANT;
private int mSynthesizerVolume = 9;//合成的音量,0-9
private int mSynthesizerSpeed = 5;//合成的语速,0-9
private int mSynthesizerPitch = 5;//设置合成的语调,0-9
private MixMode mSynthesizerMode = MixMode.MIX_MODE_DEFAULT;//网络合成模式
private TtsMode mTtsMode = TtsMode.MODE_MIX;//合成模式
private String mTextFilename;//离线文件
private String mModelFilename;//离线model文件
private SpeechSynthesizerAdapter mSpeechSynthesizerAdapter;
private Map<String, ISpeedStatusCallback> mStatusCallbackMap;
public SpeechSynthesizerManager setInformant(Informant informant) {
mInformant = informant;
return this;
}
public SpeechSynthesizerManager setSynthesizerVolume(int synthesizerVolume) {
mSynthesizerVolume = synthesizerVolume;
return this;
}
public SpeechSynthesizerManager setSynthesizerSpeed(int synthesizerSpeed) {
mSynthesizerSpeed = synthesizerSpeed;
return this;
}
public SpeechSynthesizerManager setSynthesizerPitch(int synthesizerPitch) {
mSynthesizerPitch = synthesizerPitch;
return this;
}
public SpeechSynthesizerManager setSynthesizerMode(MixMode synthesizerMode) {
mSynthesizerMode = synthesizerMode;
return this;
}
public SpeechSynthesizerManager setTtsMode(TtsMode ttsMode) {
mTtsMode = ttsMode;
return this;
}
public void setOfflineVoiceType(OfflineVoiceType offlineVoiceType) {
mOfflineVoiceType = offlineVoiceType;
}
public void setSpeechSynthesizerAdapter(SpeechSynthesizerAdapter speechSynthesizerAdapter) {
mSpeechSynthesizerAdapter = speechSynthesizerAdapter;
}
public int init(Context context, IInitCallback callback) {
mApplicationContext = context;
mStatusCallbackMap = new HashMap<>();
mMainHandler = new MainHandler(context.getMainLooper());
mSpeechSynthesizer = SpeechSynthesizer.getInstance();
mSpeechSynthesizer.setContext(mApplicationContext);
mSpeechSynthesizer.setSpeechSynthesizerListener(new SpeechSynthesizerEvent());
// 请替换为语音开发者平台上注册应用得到的App ID ,AppKey ,Secret Key ,填写在SynthActivity的开始位置
mSpeechSynthesizer.setAppId(Config.appId);
mSpeechSynthesizer.setApiKey(Config.appKey, Config.secretKey);
callback.onTtsInit();
// 初始化tts
int result = mSpeechSynthesizer.initTts(mTtsMode.mode);
mIsInitSuccess = result == 0;
return result;
}
private void startTts(Context context, ISpeedInitStatusCallback callback) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
callback.onError(-1, "暂无权限");
return;
}
if (!mIsInitSuccess) {
Observable.create((ObservableOnSubscribe<Pair<Integer, String>>) observableEmitter -> {
int result = init(context.getApplicationContext(), () -> {
boolean isMix = mTtsMode.equals(TtsMode.MODE_MIX);
if (isMix) {
if (!checkOfflineResources()) {
initOfflineResource(mOfflineVoiceType);
}
}
initParams(mSpeechSynthesizer);
if (isMix) {
// 授权检测接口(只是通过AuthInfo进行检验授权是否成功。选择纯在线可以不必调用auth方法。
AuthInfo authInfo = mSpeechSynthesizer.auth(mTtsMode.mode);
if (!authInfo.isSuccess()) {
// 离线授权需要网站上的应用填写包名。本demo的包名是com.baidu.tts.sample,定义在build.gradle中
String errorMsg = authInfo.getTtsError().getDetailMessage();
observableEmitter.onNext(new Pair<>(authInfo.getTtsError().getCode(), errorMsg));
}
}
});
if (result == 0) {
observableEmitter.onNext(new Pair<>(result, null));
} else {
observableEmitter.onNext(new Pair<>(result, "语音合成初始化失败"));
}
observableEmitter.onComplete();
}).compose(applySchedulers())
.subscribe(integerStringPair -> {
if (callback != null && integerStringPair != null) {
if (integerStringPair.first == 0) {
callback.onSuccess();
} else {
callback.onError(integerStringPair.first, integerStringPair.second);
}
}
});
} else {
callback.onSuccess();
}
}
public <T> ObservableTransformer<T, T> applySchedulers() {
return observable -> observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
private boolean checkOfflineResources() {
String[] filenames = {mTextFilename, mModelFilename};
for (String path : filenames) {
if (TextUtils.isEmpty(mTextFilename)) return false;
File f = new File(path);
if (f.exists() && f.canRead()) {
continue;
} else {
return false;
}
}
return true;
}
private void initOfflineResource(OfflineVoiceType offlineVoice) {
OfflineResource offlineResource = null;
try {
offlineResource = new OfflineResource(mApplicationContext, offlineVoice);
mTextFilename = offlineResource.getTextFilename();
mModelFilename = offlineResource.getModelFilename();
} catch (IOException e) {
// IO 错误自行处理
e.printStackTrace();
}
}
private void initParams(SpeechSynthesizer speechSynthesizer) {
speechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, String.valueOf(mInformant.ordinal()));
// 设置合成的音量,0-9 ,默认 9
speechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, String.valueOf(mSynthesizerVolume));
// 设置合成的语速,0-9 ,默认 5
speechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, String.valueOf(mSynthesizerSpeed));
// 设置合成的语调,0-9 ,默认 5
speechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, String.valueOf(mSynthesizerPitch));
// 该参数设置为TtsMode.MIX生效。即纯在线模式不生效。
// MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
speechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, mSynthesizerMode.modeName);
if (!TextUtils.isEmpty(mTextFilename)) {
speechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, mTextFilename);
}
if (!TextUtils.isEmpty(mModelFilename)) {
speechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE,
mModelFilename);
}
}
private String generatorRandomUtteranceId() {
return "Ss_" + Math.random() * 1000;
}
private void resetAudioFocus(Context context, ISpeedFocusCallback callback) {
if (mAudioFocusChangeListener == null) {
mAudioFocusChangeListener = new AudioFocuseChangeCallback();
}
if (mAudioManager == null) {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
if (mAudioManager != null) {
int result = mAudioManager.requestAudioFocus(mAudioFocusChangeListener,
AudioManager.STREAM_MUSIC,// Use the music stream.
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);// Request permanent focus.
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
callback.onFocusGain(true);
} else {
callback.onFocusGain(false);
}
} else {
callback.onFocusGain(false);
}
}
private void releaseAudioFocus() {
if (mAudioManager != null) {
mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
}
}
@Override
public void speak(Context context, ISpeedStatusCallback callback, String text) {
speakWithId(context, callback, text, generatorRandomUtteranceId());
}
@Override
public void speakWithId(Context context, ISpeedStatusCallback callback, String text, String utteranceId) {
resetAudioFocus(context, gain -> startTts(context, new ISpeedInitStatusCallback() {
@Override
public void onError(int code, String des) {
if (callback != null) callback.onError(code, des);
}
@Override
public void onSuccess() {
if (callback != null) mStatusCallbackMap.put(utteranceId, callback);
int result = mSpeechSynthesizer.speak(text, utteranceId);
if (result != 0 && callback != null) {
callback.onError(result, "合成播放失败");
}
}
}));
}
@Override
public void synthesize(Context context, ISpeedStatusCallback callback, String text) {
synthesizeWithId(context, callback, text, generatorRandomUtteranceId());
}
@Override
public void synthesizeWithId(Context context, ISpeedStatusCallback callback, String text, String utteranceId) {
startTts(context, new ISpeedInitStatusCallback() {
@Override
public void onError(int code, String des) {
if (callback != null) callback.onError(code, des);
}
@Override
public void onSuccess() {
if (callback != null) mStatusCallbackMap.put(utteranceId, callback);
int result = mSpeechSynthesizer.synthesize(text, utteranceId);
if (result != 0 && callback != null) {
callback.onError(result, "合成失败");
}
}
});
}
@Override
public void batchSpeak(Context context, ISpeedStatusCallback callback, List<Pair<String, String>> texts) {
resetAudioFocus(context, gain -> startTts(context, new ISpeedInitStatusCallback() {
@Override
public void onError(int code, String des) {
if (callback != null) callback.onError(code, des);
}
@Override
public void onSuccess() {
List<SpeechSynthesizeBag> bags = new ArrayList<SpeechSynthesizeBag>();
for (Pair<String, String> pair : texts) {
SpeechSynthesizeBag speechSynthesizeBag = new SpeechSynthesizeBag();
speechSynthesizeBag.setText(pair.first);
if (pair.second != null) {
speechSynthesizeBag.setUtteranceId(pair.second);
}
bags.add(speechSynthesizeBag);
}
int result = mSpeechSynthesizer.batchSpeak(bags);
if (result != 0 && callback != null) {
callback.onError(result, "批量合成播放失败");
}
}
}));
}
@Override
public void pause(ISpeedStatusCallback callback) {
if (mSpeechSynthesizer != null) {
int result = mSpeechSynthesizer.pause();
if (result != 0 && callback != null) {
callback.onError(result, "暂停合成播放失败");
}
} else {
if (callback != null) {
callback.onError(-1, "未初始化");
}
}
}
@Override
public void resume(ISpeedStatusCallback callback) {
if (mSpeechSynthesizer != null) {
int result = mSpeechSynthesizer.resume();
if (result != 0 && callback != null) {
callback.onError(result, "重启合成播放失败");
}
} else {
if (callback != null) {
callback.onError(-1, "未初始化");
}
}
}
@Override
public void stop(ISpeedStatusCallback callback) {
if (mSpeechSynthesizer != null) {
int result = mSpeechSynthesizer.stop();
if (result != 0 && callback != null) {
callback.onError(result, "取消合成播放失败");
}
} else {
if (callback != null) {
callback.onError(-1, "未初始化");
}
}
}
@Override
public void setStereoVolume(float leftVolume, float rightVolume) {
if (mSpeechSynthesizer != null) {
mSpeechSynthesizer.setStereoVolume(leftVolume, rightVolume);
}
}
@Override
public void loadModel(Context context, ISpeedStatusCallback callback, String modelFilename, String textFilename) {
startTts(context, new ISpeedInitStatusCallback() {
@Override
public void onError(int code, String des) {
if (callback != null) callback.onError(code, des);
}
@Override
public void onSuccess() {
int result = mSpeechSynthesizer.loadModel(modelFilename, textFilename);
if (result != 0 && callback != null) {
callback.onError(result, "加载离线失败");
}
}
});
}
@Override
public void release(ISpeedStatusCallback callback) {
if (mSpeechSynthesizer != null) {
int result = mSpeechSynthesizer.release();
if (result != 0 && callback != null) {
callback.onError(result, "释放合成播放失败");
}
} else {
if (callback != null) {
callback.onError(-1, "未初始化");
}
}
}
public class SpeechSynthesizerEvent implements SpeechSynthesizerListener {
private void start(String utteranceId) {
ISpeedStatusCallback callback = mStatusCallbackMap.get(utteranceId);
if (callback != null) {
mMainHandler.post(() -> callback.onStart(utteranceId));
}
}
private void finish(String utteranceId) {
ISpeedStatusCallback callback = mStatusCallbackMap.get(utteranceId);
if (callback != null) {
mMainHandler.post(() -> callback.onFinish(utteranceId));
mStatusCallbackMap.remove(utteranceId);
}
}
private void error(int code, String utteranceId) {
ISpeedStatusCallback callback = mStatusCallbackMap.get(utteranceId);
if (callback != null) {
mMainHandler.post(() -> callback.onError(code, utteranceId));
mStatusCallbackMap.remove(utteranceId);
}
}
@Override
public void onSynthesizeStart(String s) {
start(s);
if (mSpeechSynthesizerAdapter != null) {
mMainHandler.post(() -> mSpeechSynthesizerAdapter.onSynthesizeStart(s));
}
}
@Override
public void onSynthesizeDataArrived(String s, byte[] bytes, int i) {
if (mSpeechSynthesizerAdapter != null) {
mMainHandler.post(() -> mSpeechSynthesizerAdapter.onSynthesizeDataArrived(s, bytes, i));
}
}
@Override
public void onSynthesizeFinish(String s) {
if (mSpeechSynthesizerAdapter != null) {
mMainHandler.post(() -> mSpeechSynthesizerAdapter.onSynthesizeFinish(s));
}
}
@Override
public void onSpeechStart(String s) {
if (mSpeechSynthesizerAdapter != null) {
mMainHandler.post(() -> mSpeechSynthesizerAdapter.onSpeechStart(s));
}
}
@Override
public void onSpeechProgressChanged(String s, int i) {
if (mSpeechSynthesizerAdapter != null) {
mMainHandler.post(() -> mSpeechSynthesizerAdapter.onSpeechProgressChanged(s, i));
}
}
@Override
public void onSpeechFinish(String s) {
finish(s);
if (mSpeechSynthesizerAdapter != null) {
mMainHandler.post(() -> mSpeechSynthesizerAdapter.onSpeechFinish(s));
}
releaseAudioFocus();
}
@Override
public void onError(String s, SpeechError speechError) {
error(speechError.code, speechError.description);
if (mSpeechSynthesizerAdapter != null) {
mMainHandler.post(() -> mSpeechSynthesizerAdapter.onError(s, speechError.description, speechError.code));
}
}
}
public class AudioFocuseChangeCallback implements AudioManager.OnAudioFocusChangeListener {
@Override
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Resume playback
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
//mAudioManager.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
// Stop playback
}
}
}
public static class MainHandler extends Handler {
public MainHandler(Looper looper) {
super(looper);
}
}
}
package com.hikcreate.baiduaudiodect.listener;
import android.content.Context;
import android.util.Pair;
import java.util.List;
/**
* @author yslei
* @date 2019/6/17
* @email leiyongsheng@hikcreate.com
*/
public abstract class ASpeechSynthesizerManager implements ISpeechSynthesizerManager {
public void speak(Context context, String text) {
speak(context, null, text);
}
public void speakWithId(Context context, String text, String utteranceId) {
speakWithId(context, null, text, utteranceId);
}
public void synthesize(Context context, String text) {
synthesize(context, null, text);
}
public void synthesizeWithId(Context context, String text, String utteranceId) {
synthesizeWithId(context, null, text, utteranceId);
}
public void batchSpeak(Context context, List<Pair<String, String>> texts) {
batchSpeak(context, null, texts);
}
public void pause() {
pause(null);
}
public void resume() {
release(null);
}
public void stop() {
stop(null);
}
public void loadModel(Context context, String modelFilename, String textFilename) {
loadModel(context, null, modelFilename, textFilename);
}
public void release() {
release(null);
}
}
package com.hikcreate.baiduaudiodect.listener;
/**
* @author yslei
* @date 2019/6/17
* @email leiyongsheng@hikcreate.com
*/
public interface IInitCallback {
void onTtsInit();
}
package com.hikcreate.baiduaudiodect.listener;
/**
* @author yslei
* @date 2019/6/14
* @email leiyongsheng@hikcreate.com
*/
public interface ISpeechSynthesizerCallback {
void onSynthesizeStart(String utteranceId);
void onSynthesizeDataArrived(String utteranceId, byte[] bytes, int progress);
void onSynthesizeFinish(String utteranceId);
void onSpeechStart(String utteranceId);
void onSpeechProgressChanged(String utteranceId, int progress);
void onSpeechFinish(String utteranceId);
void onError(String utteranceId, String des, int code);
}
package com.hikcreate.baiduaudiodect.listener;
import android.content.Context;
import android.util.Pair;
import java.util.List;
/**
* @author yslei
* @date 2019/6/17
* @email leiyongsheng@hikcreate.com
*/
public interface ISpeechSynthesizerManager {
/**
* 合成并播放
*
* @param text 小于1024 GBK字节,即512个汉字或者字母数字
* @return
*/
void speak(Context context, ISpeedStatusCallback callback, String text);
/**
* 合成并播放
*
* @param text 小于1024 GBK字节,即512个汉字或者字母数字
* @param utteranceId 用于listener的回调,默认"0"
* @return
*/
void speakWithId(Context context, ISpeedStatusCallback callback, String text, String utteranceId);
/**
* 只合成不播放
*
* @param text
* @return
*/
void synthesize(Context context, ISpeedStatusCallback callback, String text);
/**
* 只合成不播放
*
* @param text
* @return
*/
void synthesizeWithId(Context context, ISpeedStatusCallback callback, String text, String utteranceId);
/**
* 批量合成并播放
*
* @param texts
* @return
*/
void batchSpeak(Context context, ISpeedStatusCallback callback, List<Pair<String, String>> texts);
/**
* 暂停
*
* @return
*/
void pause(ISpeedStatusCallback callback);
/**
* 重置播放
*
* @return
*/
void resume(ISpeedStatusCallback callback);
/**
* 停止退出
*
* @return
*/
void stop(ISpeedStatusCallback callback);
/**
* 设置播放音量,默认已经是最大声音
* 0.0f为最小音量,1.0f为最大音量
*
* @param leftVolume [0-1] 默认1.0f
* @param rightVolume [0-1] 默认1.0f
*/
void setStereoVolume(float leftVolume, float rightVolume);
/**
* 引擎在合成时该方法不能调用!!!
* 注意 只有 TtsMode.MIX 才可以切换离线发音
*
* @return
*/
void loadModel(Context context, ISpeedStatusCallback callback, String modelFilename, String textFilename);
/**
* 释放
*
* @return
*/
void release(ISpeedStatusCallback callback);
}
package com.hikcreate.baiduaudiodect.listener;
/**
* @author yslei
* @date 2019/6/17
* @email leiyongsheng@hikcreate.com
*/
public interface ISpeedFocusCallback {
void onFocusGain(boolean gain);
}
package com.hikcreate.baiduaudiodect.listener;
/**
* @author yslei
* @date 2019/6/17
* @email leiyongsheng@hikcreate.com
*/
public interface ISpeedInitStatusCallback {
void onError(int code, String des);
void onSuccess();
}
package com.hikcreate.baiduaudiodect.listener;
/**
* @author yslei
* @date 2019/6/17
* @email leiyongsheng@hikcreate.com
*/
public interface ISpeedStatusCallback {
void onError(int code, String des);
void onStart(String utteranceId);
void onFinish(String utteranceId);
}
package com.hikcreate.baiduaudiodect.listener;
/**
* @author yslei
* @date 2019/6/14
* @email leiyongsheng@hikcreate.com
*/
public class SpeechSynthesizerAdapter implements ISpeechSynthesizerCallback {
@Override
public void onSynthesizeStart(String utteranceId) {
}
@Override
public void onSynthesizeDataArrived(String utteranceId, byte[] bytes, int progress) {
}
@Override
public void onSynthesizeFinish(String utteranceId) {
}
@Override
public void onSpeechStart(String utteranceId) {
}
@Override
public void onSpeechProgressChanged(String utteranceId, int progress) {
}
@Override
public void onSpeechFinish(String utteranceId) {
}
@Override
public void onError(String utteranceId, String des, int code) {
}
}
package com.hikcreate.baiduaudiodect.model;
/**
* @author yslei
* @date 2019/6/14
* @email leiyongsheng@hikcreate.com
*/
public class Config {
public final static String appId = "16122149";
public final static String appKey = "ZBlPogkwdOPsQy0mdaZd0iuEW9EfVsoR";
public final static String secretKey = "eCaO8xWDSY5v4B2elbQlHCHsdIaQ6qIf";
}
package com.hikcreate.baiduaudiodect.model;
/**
* 发音人设置
* 设置在线发声音人: 0 普通女声(默认) 1 普通男声 2 特别男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
*
* @author yslei
* @date 2019/6/14
* @email leiyongsheng@hikcreate.com
*/
public enum Informant {
COMMON_FEMALE_INFORMANT,
COMMON_MALE_INFORMANT,
SPECIAL_MALE_INFORMANT,
EMOTION_DXY_INFORMANT,
EMOTION_DYY_INFORMANT
}
package com.hikcreate.baiduaudiodect.model;
import com.baidu.tts.client.SpeechSynthesizer;
/**
* 该参数设置为TtsMode.MIX生效。即纯在线模式不生效。
* MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
* MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
* MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
* MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
*
* @author yslei
* @date 2019/6/14
* @email leiyongsheng@hikcreate.com
*/
public enum MixMode {
MIX_MODE_DEFAULT(SpeechSynthesizer.MIX_MODE_DEFAULT),
MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI(SpeechSynthesizer.MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI),
MIX_MODE_HIGH_SPEED_NETWORK(SpeechSynthesizer.MIX_MODE_HIGH_SPEED_NETWORK),
MIX_MODE_HIGH_SPEED_SYNTHESIZE(SpeechSynthesizer.MIX_MODE_HIGH_SPEED_SYNTHESIZE);
public String modeName;
MixMode(String modeName) {
this.modeName = modeName;
}
}
package com.hikcreate.baiduaudiodect.model;
/**
* 离线发音人设置
* 设置在线发声音人: 0 普通女声(默认) 1 普通男声 2 情感男声<度逍遥> 3 情感儿童声<度丫丫>
*
* @author yslei
* @date 2019/6/14
* @email leiyongsheng@hikcreate.com
*/
public enum OfflineVoiceType {
COMMON_FEMALE_OFFLINE_INFORMANT,
COMMON_MALE_OFFLINE_INFORMANT,
EMOTION_DXY_OFFLINE_INFORMANT,
EMOTION_DYY_OFFLINE_INFORMANT
}
package com.hikcreate.baiduaudiodect.model;
/**
* TtsMode.MIX; 离在线融合,在线优先; TtsMode.ONLINE 纯在线; 没有纯离线
*
* @author yslei
* @date 2019/6/14
* @email leiyongsheng@hikcreate.com
*/
public enum TtsMode {
MODE_MIX(com.baidu.tts.client.TtsMode.MIX),
MODE_ONLINE(com.baidu.tts.client.TtsMode.ONLINE);
public com.baidu.tts.client.TtsMode mode;
TtsMode(com.baidu.tts.client.TtsMode mode) {
this.mode = mode;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:id="@+id/synthButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:text="离在线语音合成"
android:textSize="16sp" />
<Button
android:id="@+id/saveTtsFileButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:text="保存合成后的音频"
android:textSize="16sp" />
<Button
android:id="@+id/miniButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:text="精简版合成"
android:textSize="16sp" />
<Button
android:id="@+id/mineButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:text="重构合成"
android:textSize="16sp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:weightSum="3">
<Button
android:id="@+id/speak"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="合成并播放"
android:textSize="12dp" />
<Button
android:id="@+id/stop"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="2"
android:lines="2"
android:text="停止合成引擎"
android:textSize="12dp" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/btn"
>
<TextView
android:id="@+id/showText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:background="@android:color/darker_gray"
android:minLines="3"
android:scrollbars="vertical" />
</ScrollView>
</LinearLayout>
<?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:orientation="vertical">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:weightSum="4">
<Button
android:id="@+id/speak"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="合成并播放"
android:textSize="12dp" />
<Button
android:id="@+id/synthesize"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="只合成不播放"
android:textSize="12dp" />
<Button
android:id="@+id/batchSpeak"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="批量合成并播放"
android:textSize="12dp" />
<Button
android:id="@+id/loadModel"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="切换离线发音人"
android:textSize="12dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:weightSum="4">
<Button
android:id="@+id/pause"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="播放暂停"
android:textSize="12dp" />
<Button
android:id="@+id/resume"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="播放恢复"
android:textSize="12dp" />
<Button
android:id="@+id/stop"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="停止合成引擎"
android:textSize="12dp" />
<Button
android:id="@+id/help"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="使用说明"
android:textSize="12dp" />
</LinearLayout>
<LinearLayout
android:layout_width="0px"
android:layout_height="0px"
android:focusable="true"
android:focusableInTouchMode="true" />
<EditText
android:id="@+id/input"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="input" />
<TextView
android:id="@+id/showText"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="10dp"
android:background="@android:color/darker_gray"
android:minLines="3"
android:scrollbars="vertical" />
</LinearLayout>
\ No newline at end of file
<resources>
<string name="app_name">BaiduTtsSample</string>
</resources>
......@@ -9,19 +9,6 @@ android {
versionName rootProject.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debugg {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
......
include ':app',":lib_network", ':app_passport', ':app_login'
include ':app',":lib_network", ':lib_app_common', ':app_passport', ':app_login', ':lib_baidu_audiodect',':buildsrc'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment