Android 百度语音合成 (含离线、在线、API合成方式,详细步骤+源码) | 您所在的位置:网站首页 › 百度ai在线语音合成 › Android 百度语音合成 (含离线、在线、API合成方式,详细步骤+源码) |
百度语音合成
声明前言正文一、创建项目二、离线语音合成1. 配置AndroidManifest.xml2. 配置SDK3. 离线SDK初始化4. 导包5. 运行
三、在线语音合成 - SDK方式1. 创建页面2. 编辑代码3. 配置4. 运行
四、在线语音合成 - API方式1. 鉴权返回实体2. 添加框架依赖3. 搭建网络请求框架4. 编辑布局和页面5. 获取鉴权Token6. 动态权限请求7. Api语音合成8. 音频文件下载9. 播放
五、源码
声明
本文代码请使用真机运行,别用模拟器虚拟机,谢谢! 前言我之前写过百度的语音识别,也写过讯飞的语音识别与合成,而有读者看完后说没有百度的语音合成,想在用百度语音识别的同时使用百度的语音合成。所以就有了这篇文章,我的文章也是区别于其他人的文章,所以我有自己的风格。 感兴趣可以先扫码下载体验一下,再决定往不往下面看。 首先我们登录这个百度智能云,然后找到语音技术。
点击左侧的离线合成SDK 注意这个还要激活SDK才行的。激活是需要序列号的,那么这个序列号那里来呢?点击查看详情 下载这个SDK 下载后解压,下面正式来配置这个离线的语音合成了。 1. 配置AndroidManifest.xml打开项目的AndroidManifest.xml,添加权限。 然后适配api 28以上版本。 添加位置如下图。 打开刚才的SDK,进入到libs文件夹下 进入到main文件夹下 离线SDK第一次初始化的时候需要联网,进行网络鉴权,鉴权成功之后就可以断网使用了,先完成这个初始化操作。修activity_main.xml 然后在MainActivity中写入一个方法: /** * 离线SDK合成 * @param view */ public void offlineSDK(View view) { }当点击这个方法时会进入到离线SDK的页面,这个页面现在还没有的,不过我们的下载SDK里面有现成的,那就拿过来就用好了。 首先将layout下的activity_synth.xml文件复制到项目的layout下。
然后依次打开里面的粘贴过来的类,首先是control包下的InitConfig类,里面是会有报错的,因为包名不一致。所以需要重新导包。 修改MainActivity中的代码 /** * 离线SDK合成 * @param view */ public void offlineSDK(View view) { startActivity(new Intent(this,SynthActivity.class)); }点击这个按钮跳转到SynthActivity中。别忘了要在AndroidManifest.xml中注册这个Activity。 下面运行一下:
在线合成的方式其实和离线差不了多少,在com.llw.speechsynthesis包下新建一个OnlineActivity,布局是activity_online.xml,布局代码如下: 下面再来看OnlineActivity的代码 2. 编辑代码 package com.llw.speechsynthesis; import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.baidu.tts.chainofresponsibility.logger.LoggerProxy; import com.baidu.tts.client.SpeechSynthesizer; import com.baidu.tts.client.SpeechSynthesizerListener; import com.baidu.tts.client.TtsMode; import com.llw.speechsynthesis.control.InitConfig; import com.llw.speechsynthesis.listener.UiMessageListener; import com.llw.speechsynthesis.util.Auth; import com.llw.speechsynthesis.util.AutoCheck; import com.llw.speechsynthesis.util.FileUtil; import com.llw.speechsynthesis.util.IOfflineResourceConst; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * 除了SDK,该类没有任何依赖,可以直接复制进你的项目 ** 默认TEMP_DIR = "/sdcard/baiduTTS"; // 重要!请手动将assets目录下的3个dat 文件复制到该目录 * 确保 TEXT_FILENAME 和 MODEL_FILENAME 存在 * Created by fujiayi on 2017/9/14. */ public class OnlineActivity extends AppCompatActivity implements IOfflineResourceConst { /** * 要合成的文本,可以自行改动。 */ private static final String TEXT = "欢迎使用百度语音合成,请在代码中修改合成文本"; protected String appId; protected String appKey; protected String secretKey; protected String sn; // 纯离线合成SDK授权码;离在线合成SDK没有此参数 //TtsMode.ONLINE 纯在线 private TtsMode ttsMode = TtsMode.ONLINE; private boolean isOnlineSDK = TtsMode.ONLINE.equals(DEFAULT_SDK_TTS_MODE); // ================ 纯离线sdk或者选择TtsMode.ONLINE 以下参数无用; private static final String TEMP_DIR = "/sdcard/baiduTTS"; // 重要!请手动将assets目录下的3个dat 文件复制到该目录 // 请确保该PATH下有这个文件 private static final String TEXT_FILENAME = TEMP_DIR + "/" + TEXT_MODEL; // 请确保该PATH下有这个文件 ,m15是离线男声 private static final String MODEL_FILENAME = TEMP_DIR + "/" + VOICE_MALE_MODEL; // ===============初始化参数设置完毕,更多合成参数请至getParams()方法中设置 ================= protected SpeechSynthesizer mSpeechSynthesizer; // =========== 以下为UI部分 ================================================== private TextView mShowText; protected Handler mainHandler; private String desc; // 说明文件 private static final String TAG = "MiniActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); appId = Auth.getInstance(this).getAppId(); appKey = Auth.getInstance(this).getAppKey(); secretKey = Auth.getInstance(this).getSecretKey(); sn = Auth.getInstance(this).getSn(); // 纯离线合成必须有此参数;离在线合成SDK没有此参数 desc = FileUtil.getResourceText(this, R.raw.mini_activity_description); setContentView(R.layout.activity_online); initView(); initPermission(); initTTs(); } /** * 注意此处为了说明流程,故意在UI线程中调用。 * 实际集成中,该方法一定在新线程中调用,并且该线程不能结束。具体可以参考NonBlockSyntherizer的写法 */ private void initTTs() { LoggerProxy.printable(true); // 日志打印在logcat中 boolean isSuccess; if (!isOnlineSDK) { // 检查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. 如果是纯离线SDK需要离线功能的话 if (!isOnlineSDK) { // 文本模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读 mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME); // 声学模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读 mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME); 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自动转离线 } // 5. 以下setParam 参数选填。不填写则默认值生效 // 设置在线发声音人: 0 普通女声(默认) 1 普通男声 3 情感男声 4 情感儿童声 mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0"); // 设置合成的音量,0-15 ,默认 5 mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "9"); // 设置合成的语速,0-15 ,默认 5 mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "5"); // 设置合成的语调,0-15 ,默认 5 mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5"); // mSpeechSynthesizer.setAudioStreamType(AudioManager.MODE_IN_CALL); // 调整音频输出 if (sn != null) { // 纯离线sdk这个参数必填;离在线sdk没有此参数 mSpeechSynthesizer.setParam(PARAM_SN_NAME, sn); } // x. 额外 : 自动so文件是否复制正确及上面设置的参数 Map params = new HashMap(); // 复制下上面的 mSpeechSynthesizer.setParam参数 // 上线时请删除AutoCheck的调用 if (!isOnlineSDK) { params.put(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME); params.put(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME); } // 检测参数,通过一次后可以去除,出问题再打开debug 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); checkResult(result, "initTts"); } /** * 在线SDK不需要调用,纯离线SDK会检查资源文件 * * 检查 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] 文件不存在或者不可读取,请从demo的assets目录复制同名文件到:" + f.getAbsolutePath()); print("[ERROR] 初始化失败!!!"); return false; } } return true; } private void speak() { /* 以下参数每次合成时都可以修改 * mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0"); * 设置在线发声音人: 0 普通女声(默认) 1 普通男声 3 情感男声 4 情感儿童声 * mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "5"); 设置合成的音量,0-15 ,默认 5 * mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "5"); 设置合成的语速,0-15 ,默认 5 * mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5"); 设置合成的语调,0-15 ,默认 5 * */ 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() { Button mSpeak = this.findViewById(R.id.speak); Button mStop = this.findViewById(R.id.stop); mShowText = this.findViewById(R.id.showText); mShowText.setText(desc); View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { int id = v.getId(); switch (id) { case R.id.speak: speak(); break; case R.id.stop: stop(); break; default: break; } } }; 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); } } // 下面是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_SETTINGS, Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; ArrayList toApplyList = new ArrayList(); 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, @NonNull String[] permissions, @NonNull int[] grantResults) { // 此处为android 6.0以上动态授权的回调,用户自行实现。 } } 这里的代码其实都是这个SDK中的,直接就可以使用了。我只改动了一点点。 3. 配置然后修改AndroidManifest.xml 然后在activity_main.xml中增加一个按钮。 在MainActivity中增加方法。 /** * 在线SDK合成 * @param view */ public void onlineSDK(View view) { startActivity(new Intent(this, OnlineActivity.class)); } 4. 运行下面运行:
使用API方式就稍稍有一些麻烦,因为这个设计到网络的请求,而且不是一次请求,首先进行鉴权,拿到token,然后通过Token去请求合成,下载MP3文件,首先要构建网络模块,当然我也只是简单的写一下而已。 1. 鉴权返回实体在com.llw.imagediscerndemo下新建一个model包,包下新建一个GetTokenResponse类,代码如下: package com.llw.speechsynthesis.model; /** * 获取鉴权认证Token响应实体 * * @author llw * @date 2021/5/7 16:16 */ public class GetTokenResponse { /** * refresh_token : 25.0141c302b0f460cd0500827fa31f22ce.315360000.1935736936.282335-24113250 * expires_in : 2592000 * session_key : 9mzdCS6a/7/wIFWLR8zFoYs2koSri++RGhSecVXM/vY93At4kxYRajL/xMV17MoxcTAJfadRVaSBxokIqFeQoxsZ8e3NPQ== * access_token : 24.2830c05696b214cf07bfbdf764599b39.2592000.1622968936.282335-24113250 * scope : audio_voice_assistant_get brain_enhanced_asr audio_tts_post brain_speech_realtime public brain_all_scope picchain_test_picchain_api_scope brain_asr_async wise_adapt lebo_resource_base lightservice_public hetu_basic lightcms_map_poi kaidian_kaidian ApsMisTest_Test权限 vis-classify_flower lpq_开放 cop_helloScope ApsMis_fangdi_permission smartapp_snsapi_base smartapp_mapp_dev_manage iop_autocar oauth_tp_app smartapp_smart_game_openapi oauth_sessionkey smartapp_swanid_verify smartapp_opensource_openapi smartapp_opensource_recapi fake_face_detect_开放Scope vis-ocr_虚拟人物助理 idl-video_虚拟人物助理 smartapp_component smartapp_search_plugin avatar_video_test * session_secret : 2cdde5fd8f3fd4394c1b090e2ffa2d1c */ private String refresh_token; private int expires_in; private String session_key; private String access_token; private String scope; private String session_secret; public String getRefresh_token() { return refresh_token; } public void setRefresh_token(String refresh_token) { this.refresh_token = refresh_token; } public int getExpires_in() { return expires_in; } public void setExpires_in(int expires_in) { this.expires_in = expires_in; } public String getSession_key() { return session_key; } public void setSession_key(String session_key) { this.session_key = session_key; } public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } public String getSession_secret() { return session_secret; } public void setSession_secret(String session_secret) { this.session_secret = session_secret; } }下面简单的写一个网络请求框架。 2. 添加框架依赖打开你的app的build.gradle,在dependencise{}闭包下添加如下依赖: //retrofit2 implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1' //权限请求框架 implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation "io.reactivex.rxjava2:rxjava:2.0.0"然后在android{}闭包下添加JDK1.8的支持 compileOptions { sourceCompatibility = 1.8 targetCompatibility = 1.8 }
在com.llw.speechsynthesis下新建一个network包,在这个包下新建一个NetCallBack抽象类。里面的代码如下: package com.llw.speechsynthesis.network; import android.util.Log; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; /** * 网络请求回调 * * @param */ public abstract class NetCallBack implements Callback {//这里实现了retrofit2.Callback //访问成功回调 @Override public void onResponse(Call call, Response response) {//数据返回 if (response != null && response.body() != null && response.isSuccessful()) { onSuccess(call, response); } else { onFailed(response.raw().toString()); } } //访问失败回调 @Override public void onFailure(Call call, Throwable t) { Log.d("data str", t.toString()); onFailed(t.toString()); } //数据返回 public abstract void onSuccess(Call call, Response response); //失败异常 public abstract void onFailed(String errorStr); }然后在network包下新增一个ServiceGenerator类,里面的代码如下: package com.llw.speechsynthesis.network; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; /** * 接口地址管理 * * @author llw */ public class ServiceGenerator { public static String BASE_URL = null; public static String getBaseUrl(int type) { switch (type) { case 0://鉴权地址 BASE_URL = "https://openapi.baidu.com"; break; case 1://合成地址 BASE_URL = "https://tsn.baidu.com"; break; default: break; } return BASE_URL; } /** * 创建服务 参数就是API服务 * * @param serviceClass 服务接口 * @param 泛型规范 * @return api接口服务 */ public static T createService(Class serviceClass, int type) { //创建OkHttpClient构建器对象 OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder(); //设置请求超时的时间,这里是10秒 okHttpClientBuilder.connectTimeout(20000, TimeUnit.MILLISECONDS); //消息拦截器 因为有时候接口不同在排错的时候 需要先从接口的响应中做分析。利用了消息拦截器可以清楚的看到接口返回的所有内容 HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(); //setlevel用来设置日志打印的级别,共包括了四个级别:NONE,BASIC,HEADER,BODY //BASEIC:请求/响应行 //HEADER:请求/响应行 + 头 //BODY:请求/响应航 + 头 + 体 httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); //为OkHttp添加消息拦截器 okHttpClientBuilder.addInterceptor(httpLoggingInterceptor); //在Retrofit中设置httpclient //设置地址 就是上面的固定地址,如果你是本地访问的话,可以拼接上端口号 例如 +":8080" Retrofit retrofit = new Retrofit.Builder().baseUrl(getBaseUrl(type)) //用Gson把服务端返回的json数据解析成实体 .addConverterFactory(GsonConverterFactory.create()) //放入OKHttp,之前说过retrofit是对OkHttp的进一步封装 .client(okHttpClientBuilder.build()) .build(); //返回这个创建好的API服务 return retrofit.create(serviceClass); } }下面写接口,在network包下新增ApiService接口,代码如下: package com.llw.speechsynthesis.network; import com.llw.speechsynthesis.model.GetTokenResponse; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.POST; import retrofit2.http.Streaming; /** * API服务 * * @author llw * @date 2021/5/8 10:48 */ public interface ApiService { /** * 获取鉴权认证Token * @param grant_type 类型 * @param client_id API Key * @param client_secret Secret Key * @return GetTokenResponse */ @FormUrlEncoded @POST("/oauth/2.0/token") Call getToken(@Field("grant_type") String grant_type, @Field("client_id") String client_id, @Field("client_secret") String client_secret); /** * 在线API音频合成 * @param tok 鉴权token * @param ctp 客户端类型选择,web端填写固定值1 * @param cuid 用户唯一标识,用来计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码,长度为60字符以内 * @param lan 固定值zh。语言选择,目前只有中英文混合模式,填写固定值zh * @param tex 合成的文本,使用UTF-8编码。小于2048个中文字或者英文数字,文本在百度服务器内转换为GBK后,长度必须小于4096字节(5003、5118发音人需小于512个中文字或者英文数字) * @return 正常合成之后返回一个音频文件 */ @Streaming @FormUrlEncoded @POST("/text2audio") Call synthesis(@Field("tok") String tok, @Field("ctp") String ctp, @Field("cuid") String cuid, @Field("lan") String lan, @Field("tex") String tex); }里面有两个接口,一个是用来获取鉴权Token的,另一个是用来将文字合成音频文件的。这里会比较的麻烦一些。到此为止这个简单的网络框架就写好了。 4. 编辑布局和页面在com.llw.speechsynthesis下新建一个OnlineAPIActivity,对应的布局是activity_online_api.xml,里面的代码如下: 下面先到AndroidManifest.xml中去配置Title。 先来完成页面的初始化。现在布局的控件有三个 声明变量 private static final String TAG = "OnlineAPIActivity"; /** * 输入框 */ private EditText etText; /** * 页面按钮 */ private Button btnSynthApi, btnPlay;写一个初始化页面的方法 /** * 初始化 */ private void initView() { etText = findViewById(R.id.et_text); btnSynthApi = findViewById(R.id.btn_synth_api); btnPlay = findViewById(R.id.btn_play); btnSynthApi.setOnClickListener(this); btnPlay.setOnClickListener(this); }这里我给两个按钮添加了点击的监听,那么你需要给Activity实现控件的点击监听。 然后要在onCreate方法中调用initView()方法。 声明变量 /** * Api服务 */ private ApiService service; /** * 鉴权Toeken */ private String accessToken;然后新增一个requestApiGetToken方法,代码如下: /** * 访问API获取接口 */ private void requestApiGetToken() { String grantType = "client_credentials"; String apiKey = "sKWlGNoBrNyaKaAycoiKFzdT"; String apiSecret = "OwEPWPiSnMNxCF5GFZlORKzP01KwgC1Z"; service = ServiceGenerator.createService(ApiService.class, 0); service.getToken(grantType, apiKey, apiSecret) .enqueue(new NetCallBack() { @Override public void onSuccess(Call call, Response response) { if (response.body() != null) { //鉴权Token accessToken = response.body().getAccess_token(); Log.d(TAG, accessToken); } } @Override public void onFailed(String errorStr) { Log.e(TAG, "获取Token失败,失败原因:" + errorStr); accessToken = null; } }); }这里的apiKey、apiSecret 的值改成自己平台创建应用时产生,你要是用我的,除了问题又问我为什么,我就只能。。。了。当然也要在onCreate中调用,这样我们已经入页面就会请求接口拿到鉴权Token。 下面我们运行一下,不过要先在MainActivity中写一个入口才行,在activity_main.xml中增加一个按钮。 然后在MainActivity中增加方法 /** * 在线API合成 * @param view */ public void onlineAPI(View view) { startActivity(new Intent(this,OnlineAPIActivity.class)); }那么现在你就可以运行了。
因为接口请求之后会下载一个文件到手机本地,因此你需要文件读写权限、 声明变量 /** * 权限请求框架 */ private RxPermissions rxPermissions;然后在initView中实例化。 这里也是很简单的代码,当点击在线合成API按钮时,先调用requestPermission方法进行权限的检查。 这里合成是读取页面中的文本,如果输入框的内容为空则使用默认文字进行语音合成,因此需要一个默认的文本。 声明变量 /** * 默认文本,当输入框未输入使用, */ private String defaultText = "你好!百度。";然后在权限通过的地方加上这样的一段代码 //如果输入框的内容为空则使用默认文字进行语音合成 String text; if (etText.getText().toString().trim().isEmpty()) { text = defaultText; } else { text = etText.getText().toString().trim(); }
然后在这里调用。 因为这里返回的是一个音频文件,因此不能使用常规的方式来处理,下载当然是下载的项目的缓存目录里面去,当前我在Android10.0上是可以实践的,Android11.0可能要进行分区存储才行,这里说明一下。 在listener包下新增一个DownloadListener接口,里面的代码如下: package com.llw.speechsynthesis.listener; /** * 下载监听 * * @author llw * @date 2021/5/8 9:50 */ public interface DownloadListener { /** * 开始下载 */ void onStart(); /** * 下载进度 * @param progress 当前进度 */ void onProgress(int progress); /** * 下载完成 * @param path 文件路径 */ void onFinish(String path); /** * 下载失败 * @param errorInfo 错误信息 */ void onFail(String errorInfo); }然后回到OnlineAPIActivity中,声明变量 /** * 文件路径 */ private String mPath; /** * 缓冲区大小 */ private static int sBufferSize = 8192; /** * 文件 */ private File file;接口的回调 /** * 下载文件监听 */ private DownloadListener listener = new DownloadListener() { @Override public void onStart() { Log.d(TAG, "开始"); } @Override public void onProgress(int progress) { Log.d(TAG, "进度:" + progress); } @Override public void onFinish(String path) { Log.d(TAG, "完成:" + path); mPath = path; //显示播放控件 btnPlay.setVisibility(View.VISIBLE); } @Override public void onFail(String errorInfo) { Log.d(TAG, "异常:" + errorInfo); } };然后新增一个写入磁盘的方法。 /** * 写入磁盘 * @param response 响应体 * @param downloadListener 下载监听 */ private void writeToDisk(Response response, DownloadListener downloadListener) { //开始下载 downloadListener.onStart(); //输入流 将输入流写入文件 InputStream is = response.body().byteStream(); //文件总长 long totalLength = response.body().contentLength(); //设置文件存放路径 file = new File(getExternalCacheDir() + "/Speech/" + "test.mp3"); //创建文件 if (!file.exists()) { if (!file.getParentFile().exists()) { file.getParentFile().mkdir(); } try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); downloadListener.onFail("createNewFile IOException"); } } //输出流 OutputStream os = null; long currentLength = 0; try { os = new BufferedOutputStream(new FileOutputStream(file)); byte data[] = new byte[sBufferSize]; int len; while ((len = is.read(data, 0, sBufferSize)) != -1) { os.write(data, 0, len); currentLength += len; //计算当前下载进度 downloadListener.onProgress((int) (100 * currentLength / totalLength)); } //下载完成,并返回保存的文件路径 downloadListener.onFinish(file.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); downloadListener.onFail("IOException"); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } try { if (os != null) { os.close(); } } catch (IOException e) { e.printStackTrace(); } } }然后在请求成功的分支中调用这个方法,如下图所示:
文件下载成功之后,也拿到了文件的路径了,下面就是通过这个路径去播放这个音频了。 新增一个play方法。 /** * 播放 */ private void play() { if(mPath != null){ MediaPlayer mediaPlayer = new MediaPlayer(); try { mediaPlayer.setDataSource(mPath); mediaPlayer.prepare(); mediaPlayer.start(); } catch (IOException e) { e.printStackTrace(); } } }
GitHub源码地址:SpeechSynthesisDemo CSDN源码下载:SpeechSynthesisDemo.rar 如果本文对你有所帮助,不妨点个赞或者评论一下,也可以说说你的想法和问题,我是初学者-Study,山高水长,后会有期~ |
CopyRight 2018-2019 实验室设备网 版权所有 |