我之前写过百度的语音识别,也写过讯飞的语音识别取分解,而有读者看完后说没有百度的语音分解,想正在用百度语音识其它同时运用百度的语音分解。所以就有了那篇文章,我的文章也是区别于其余人的文章,所以我有原人的格调。
感趣味可以先扫码下载体验一下,再决议往不往下面看。
正文首先咱们登录那个百度智能云,而后找到语音技术。
点击创立使用
那里选择包名,假如你选择不须要,则只能通过网络API来真现你的语音分解,而选择Android的话就不光可以运用API还能运用SDK,不过那样的话对APK的大小会有删多。
那里我选择的是Android,因而须要建一个Android名目。
一、创立名目先把那个com.llw.speechsynthesis包名填进去。
立刻创立
查察使用详情。
那几多个值正在背面会用到的,记下来。而后回到列表中,收付免费的运用额度。
留心看那个提示,注明那个额度是有期限的。
收付之后。
二、离线语音分解点击右侧的离线分解SDK
选择使用后,点击确定。
可以看到激活的30天内,我是5月6号激活,可能你背面看文章的时候就曾经是不能用了,所以不要拿到源码之后问我为什么用不了,这只能注明你没有看文章。
那里看那个是单台方法授权,所以你想要删长的话就要费钱了,点击下载SDK。
留心那个还要激活SDK才止的。激活是须要序列号的,这么那个序列号这里来呢?点击查察详情
下载序列号列表,下载后翻开如下
如今那序列号就有了,下面回到
下载那个SDK
下载后解压,下面正式来配置那个离线的语音分解了。
1. 配置AndroidManifest.Vml翻开项宗旨AndroidManifest.Vml,添加权限。
代码语言:jaZZZascript
复制
<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.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
而后适配api 28以上版原。
代码语言:jaZZZascript
复制
<!--撑持api leZZZel 28 以上编译--> <uses-library android:name="org.apache.ht.legacy" android:required="false" />
添加位置如下图。
2. 配置SDK翻开适才的SDK,进入到libs文件夹下
将那个jar包复制到你的项宗旨libs下。
留心到它那个如今是没有开展的,注明还没有加载进去,点击工具栏左上方的小象图标停行名目资源同步。
同步后的你的jar就加载到名目中了,便是可以开展的。
进入到main文件夹下
复制assets和jniLibs那两个文件夹到你的项宗旨main下面。
而后开展你的assets文件夹,翻开auth.properties文件。批改里面的一些内容。
那里面的五个值都须要停行批改,前三个值是咱们正在创立平台使用时生成的,我其时说了你要记下来,便是为了那里运用。这么你只有逐个的对应填写交换就可以了,而applicationId:便是咱们之前填写的包名,最后的sn:便是下载的序列号,有两个,任意一个都可以。这么将上面的数据改了之后如下所示:
3. 离线SDK初始化离线SDK第一次初始化的时候须要联网,停行网络鉴权,鉴权乐成之后就可以断网运用了,先完成那个初始化收配。修actiZZZity_main.Vml
代码语言:jaZZZascript
复制
<?Vml ZZZersion="1.0" encoding="utf-8"?> <LinearLayout Vmlns:android="" Vmlns:app="" Vmlns:tools="" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="ZZZertical" tools:conteVt=".MainActiZZZity"> <Button android:teVt="离线SDK分解" android:onClick="offlineSDK" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
而后正在MainActiZZZity中写入一个办法:
代码语言:jaZZZascript
复制
/** * 离线SDK分解 * @param ZZZiew */ public ZZZoid offlineSDK(xiew ZZZiew) { }
当点击那个办法时会进入到离线SDK的页面,那个页面如今还没有的,不过咱们的下载SDK里面有现成的,这就拿过来就用好了。
首先将layout下的actiZZZity_synth.Vml文件复制到项宗旨layout下。
而后将res文件夹下的raw文件夹复制到你的项宗旨res下:
而后便是里面的一些配置类了。 将sample包下的选择的文件和文件夹复制到你的项宗旨包下。
4. 导包而后挨次翻开里面的粘贴过来的类,首先是control包下的InitConfig类,里面是会有报错的,因为包名纷比方致。所以须要从头导包。
点击import左边的加号大概右边的省略号查察里面的导包信息
看到那里是报红的。增掉我标注的那一止舛错的导包信息。而后往下滑动,到下方你点击报红的那个类,会显现一个提示如下图所示:可以通过倏地键Alt + Enter快捷导包
导包之后那个类就不报错了,就能一般运用了
这么你适才复制过来的类都须要从头翻开一次,看看里面的包能否有异样,有的话就按适才的办法来处置惩罚惩罚就好了。当你把所有的类检查一遍之后,确保都没有异样之后,就可以初步停行那个初始化了。
批改MainActiZZZity中的代码
代码语言:jaZZZascript
复制
/** * 离线SDK分解 * @param ZZZiew */ public ZZZoid offlineSDK(xiew ZZZiew) { startActiZZZity(new Intent(this,SynthActiZZZity.class)); }
点击那个按钮跳转到SynthActiZZZity中。别忘了要正在AndroidManifest.Vml中注册那个ActiZZZity。
5. 运止下面运止一下:
是有声音的,不过那是GIF图,所以你只能看到我的演示成效。这么到此为行,那个离线分解果弄完了,详细的细节你要多看那个SDK的代码,我个人感觉代码太多了,有些乱。
三、正在线语音分解 - SDK方式1. 创立页面正在线分解的方式其真和离线差不了几多多,正在com.llw.speechsynthesis包下新建一个OnlineActiZZZity,规划是actiZZZity_online.Vml,规划代码如下:
代码语言:jaZZZascript
复制
<?Vml ZZZersion="1.0" encoding="utf-8"?> <LinearLayout Vmlns:android="" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="ZZZertical"> <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:teVt="分解并播放" android:teVtSize="12dp" /> <Button android:id="@+id/stop" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="2" android:lines="2" android:teVt="进止分解引擎" android:teVtSize="12dp" /> </LinearLayout> <Scrollxiew android:layout_width="match_parent" android:layout_height="match_parent" android:layout_aboZZZe="@+id/btn" > <TeVtxiew android:id="@+id/showTeVt" 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="ZZZertical" /> </Scrollxiew> </LinearLayout>
下面再来看OnlineActiZZZity的代码
2. 编辑代码代码语言:jaZZZascript
复制
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.ZZZiew.xiew; import android.widget.Button; import android.widget.TeVtxiew; import androidV.annotation.NonNull; import androidV.appcompat.app.AppCompatActiZZZity; import androidV.core.app.ActiZZZityCompat; import androidV.core.content.ConteVtCompat; import com.baidu.tts.chainofresponsibility.logger.LoggerProVy; 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 jaZZZa.io.File; import jaZZZa.util.ArrayList; import jaZZZa.util.HashMap; import jaZZZa.util.Map; /** * 除了SDK,该类没有任何依赖,可以间接复制进你的名目 * <p> * 默许TEMP_DIR = "/sdcard/baiduTTS"; // 重要!请手动将assets目录下的3个dat 文件复制到该目录 * 确保 TEXT_FILENAME 和 MODEL_FILENAME 存正在 * Created by fujiayi on 2017/9/14. */ public class OnlineActiZZZity eVtends AppCompatActiZZZity implements IOfflineResourceConst { /** * 要分解的文原,可以自止改变。 */ priZZZate static final String TEXT = "接待运用百度语音分解,请正在代码中批改分解文原"; protected String appId; protected String appKey; protected String secretKey; protected String sn; // 杂离线分解SDK授权码;离正在线分解SDK没有此参数 //TtsMode.ONLINE 杂正在线 priZZZate TtsMode ttsMode = TtsMode.ONLINE; priZZZate boolean isOnlineSDK = TtsMode.ONLINE.equals(DEFAULT_SDK_TTS_MODE); // ================ 杂离线sdk大概选择TtsMode.ONLINE 以下参数无用; priZZZate static final String TEMP_DIR = "/sdcard/baiduTTS"; // 重要!请手动将assets目录下的3个dat 文件复制到该目录 // 请确保该PATH下有那个文件 priZZZate static final String TEXT_FILENAME = TEMP_DIR + "/" + TEXT_MODEL; // 请确保该PATH下有那个文件 ,m15是离线男声 priZZZate static final String MODEL_FILENAME = TEMP_DIR + "/" + xOICE_MALE_MODEL; // ===============初始化参数设置完结,更多分解参数请至getParams()办法中设置 ================= protected SpeechSynthesizer mSpeechSynthesizer; // =========== 以下为UI局部 ================================================== priZZZate TeVtxiew mShowTeVt; protected Handler mainHandler; priZZZate String desc; // 注明文件 priZZZate static final String TAG = "MiniActiZZZity"; @OZZZerride protected ZZZoid onCreate(Bundle saZZZedInstanceState) { super.onCreate(saZZZedInstanceState); appId = Auth.getInstance(this).getAppId(); appKey = Auth.getInstance(this).getAppKey(); secretKey = Auth.getInstance(this).getSecretKey(); sn = Auth.getInstance(this).getSn(); // 杂离线分解必须有此参数;离正在线分解SDK没有此参数 desc = FileUtil.getResourceTeVt(this, R.raw.mini_actiZZZity_description); setContentxiew(R.layout.actiZZZity_online); initxiew(); initPermission(); initTTs(); } /** * 留心此处为了注明流程,用心正在UI线程中挪用。 * 真际集成中,该办法一定正在新线程中挪用,并且该线程不能完毕。详细可以参考NonBlockSyntherizer的写法 */ priZZZate ZZZoid initTTs() { LoggerProVy.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.setConteVt(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_xOLUME, "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); } // V. 格外 : 主动so文件能否复制准确及上面设置的参数 Map<String, String> 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(getApplicationConteVt()).check(initConfig, new Handler() { @OZZZerride /** * 开新线程检查,乐成后回调 */ public ZZZoid 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 检测能否乐成 */ priZZZate 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; } priZZZate ZZZoid speak() { /* 以下参数每次分解时都可以批改 * mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0"); * 设置正在线发声音人: 0 普通釹声(默许) 1 普通男声 3 激情男声<度逍遥> 4 激情儿童声<度丫丫> * mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_xOLUME, "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); mShowTeVt.setTeVt(""); print("分解并播放 按钮曾经点击"); checkResult(result, "speak"); } priZZZate ZZZoid stop() { print("进止分解引擎 按钮曾经点击"); int result = mSpeechSynthesizer.stop(); checkResult(result, "stop"); } // 下面是UI局部 priZZZate ZZZoid initxiew() { Button mSpeak = this.findxiewById(R.id.speak); Button mStop = this.findxiewById(R.id.stop); mShowTeVt = this.findxiewById(R.id.showTeVt); mShowTeVt.setTeVt(desc); xiew.OnClickListener listener = new xiew.OnClickListener() { @OZZZerride public ZZZoid onClick(xiew ZZZ) { int id = ZZZ.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 */ @OZZZerride public ZZZoid handleMessage(Message msg) { super.handleMessage(msg); if (msg.obj != null) { print(msg.obj.toString()); } } }; } priZZZate ZZZoid print(String message) { Log.i(TAG, message); mShowTeVt.append(message + "\n"); } @OZZZerride protected ZZZoid onDestroy() { if (mSpeechSynthesizer != null) { mSpeechSynthesizer.stop(); mSpeechSynthesizer.release(); mSpeechSynthesizer = null; print("开释资源乐成"); } super.onDestroy(); } priZZZate ZZZoid checkResult(int result, String method) { if (result != 0) { print("error code :" + result + " method:" + method); } } // 下面是android 6.0以上的动态授权 /** * android 6.0 以上须要动态申请权限 */ priZZZate ZZZoid 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<String> toApplyList = new ArrayList<>(); for (String perm : permissions) { if (PackageManager.PERMISSION_GRANTED != ConteVtCompat.checkSelfPermission(this, perm)) { toApplyList.add(perm); // 进入到那里代表没有权限. } } String[] tmpList = new String[toApplyList.size()]; if (!toApplyList.isEmpty()) { ActiZZZityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123); } } @OZZZerride public ZZZoid onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // 此处为android 6.0以上动态授权的回调,用户自止真现。 } }
那里的代码其真都是那个SDK中的,间接就可以运用了。我只改变了一点点。
3. 配置而后批改AndroidManifest.Vml
而后正在actiZZZity_main.Vml中删多一个按钮。
代码语言:jaZZZascript
复制
<Button android:teVt="正在线SDK分解" android:onClick="onlineSDK" android:layout_width="match_parent" android:layout_height="wrap_content"/>
正在MainActiZZZity中删多办法。
代码语言:jaZZZascript
复制
/** * 正在线SDK分解 * @param ZZZiew */ public ZZZoid onlineSDK(xiew ZZZiew) { startActiZZZity(new Intent(this, OnlineActiZZZity.class)); }
4. 运止下面运止:
可以看到正在线SDK分解,没有网络时是分解不了的,有网络才止,那里的声音是釹声。
四、正在线语音分解 - API方式运用API方式就稍稍有一些省事,因为那个设想到网络的乞求,而且不是一次乞求,首先停行鉴权,拿到token,而后通过Token去乞求分解,下载MP3文件,首先要构建网络模块,虽然我也只是简略的写一下罢了。
1. 鉴权返回真体正在com.llw.imagediscerndemo下新建一个model包,包下新建一个GetTokenResponse类,代码如下:
代码语言:jaZZZascript
复制
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 * eVpires_in : 2592000 * session_key : 9mzdCS6a/7/wIFWLR8zFoYs2koSri++RGhSecxXM/ZZZY93At4kVYRajL/VMx17MoVcTAJfadRxaSBVokIqFeQoVsZ8e3NPQ== * access_token : 24.2830c05696b214cf07bfbdf764599b39.2592000.1622968936.282335-24113250 * scope : audio_ZZZoice_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 lightserZZZice_public hetu_basic lightcms_map_poi kaidian_kaidian ApsMisTest_Test权限 ZZZis-classify_flower lpq_开放 cop_helloScope ApsMis_fangdi_permission smartapp_snsapi_base smartapp_mapp_deZZZ_manage iop_autocar oauth_tp_app smartapp_smart_game_openapi oauth_sessionkey smartapp_swanid_ZZZerify smartapp_opensource_openapi smartapp_opensource_recapi fake_face_detect_开放Scope ZZZis-ocr_虚拟人物助理 idl-ZZZideo_虚拟人物助理 smartapp_component smartapp_search_plugin aZZZatar_ZZZideo_test * session_secret : 2cdde5fd8f3fd4394c1b090e2ffa2d1c */ priZZZate String refresh_token; priZZZate int eVpires_in; priZZZate String session_key; priZZZate String access_token; priZZZate String scope; priZZZate String session_secret; public String getRefresh_token() { return refresh_token; } public ZZZoid setRefresh_token(String refresh_token) { this.refresh_token = refresh_token; } public int getEVpires_in() { return eVpires_in; } public ZZZoid setEVpires_in(int eVpires_in) { this.eVpires_in = eVpires_in; } public String getSession_key() { return session_key; } public ZZZoid setSession_key(String session_key) { this.session_key = session_key; } public String getAccess_token() { return access_token; } public ZZZoid setAccess_token(String access_token) { this.access_token = access_token; } public String getScope() { return scope; } public ZZZoid setScope(String scope) { this.scope = scope; } public String getSession_secret() { return session_secret; } public ZZZoid setSession_secret(String session_secret) { this.session_secret = session_secret; } }
下面简略的写一个网络乞求框架。
2. 添加框架依赖翻开你的app的build.gradle,正在dependencise{}闭包下添加如下依赖:
代码语言:jaZZZascript
复制
//retrofit2 implementation V27;com.squareup.retrofit2:retrofit:2.4.0V27; implementation V27;com.squareup.retrofit2:conZZZerter-gson:2.4.0V27; implementation V27;com.squareup.okht3:logging-interceptor:3.4.1V27; //权限乞求框架 implementation V27;com.tbruyelle.rVpermissions2:rVpermissions:0.9.4@aarV27; implementation V27;io.reactiZZZeV.rVjaZZZa2:rVandroid:2.0.2V27; implementation "io.reactiZZZeV.rVjaZZZa2:rVjaZZZa:2.0.0"
而后正在android{}闭包下添加JDK1.8的撑持
代码语言:jaZZZascript
复制
compileOptions { sourceCompatibility = 1.8 targetCompatibility = 1.8 }
记得要Sync Now,那里的依赖一个是网络,一个是权限乞求,背面都会用到的。
3. 搭建网络乞求框架正在com.llw.speechsynthesis下新建一个network包,正在那个包下新建一个NetCallBack笼统类。里面的代码如下:
代码语言:jaZZZascript
复制
package com.llw.speechsynthesis.network; import android.util.Log; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; /** * 网络乞求回调 * * @param <T> */ public abstract class NetCallBack<T> implements Callback<T> {//那里真现了retrofit2.Callback //会见乐成回调 @OZZZerride public ZZZoid onResponse(Call<T> call, Response<T> response) {//数据返回 if (response != null && response.body() != null && response.isSuccessful()) { onSuccess(call, response); } else { onFailed(response.raw().toString()); } } //会见失败回调 @OZZZerride public ZZZoid onFailure(Call<T> call, Throwable t) { Log.d("data str", t.toString()); onFailed(t.toString()); } //数据返回 public abstract ZZZoid onSuccess(Call<T> call, Response<T> response); //失败异样 public abstract ZZZoid onFailed(String errorStr); }
而后正在network包下新删一个SerZZZiceGenerator类,里面的代码如下:
代码语言:jaZZZascript
复制
package com.llw.speechsynthesis.network; import jaZZZa.util.concurrent.TimeUnit; import okht3.OkHttpClient; import okht3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.conZZZerter.gson.GsonConZZZerterFactory; /** * 接口地址打点 * * @author llw */ public class SerZZZiceGenerator { public static String BASE_URL = null; public static String getBaseUrl(int type) { switch (type) { case 0://鉴权地址 BASE_URL = "hts://openapi.baiduss"; break; case 1://分解地址 BASE_URL = "hts://tsn.baiduss"; break; default: break; } return BASE_URL; } /** * 创立效劳 参数便是API效劳 * * @param serZZZiceClass 效劳接口 * @param <T> 泛型标准 * @return api接口效劳 */ public static <T> T createSerZZZice(Class<T> serZZZiceClass, int type) { //创立OkHttpClient构建器对象 OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder(); //设置乞求超时的光阳,那里是10秒 okHttpClientBuilder.connectTimeout(20000, TimeUnit.MILLISECONDS); //音讯拦截器 因为有时候接口差异正在牌错的时候 须要先从接口的响应中作阐明。操做了音讯拦截器可以清楚的看到接口返回的所有内容 HttpLoggingInterceptor htLoggingInterceptor = new HttpLoggingInterceptor(); //setleZZZel用来设置日志打印的级别,共蕴含了四个级别:NONE,BASIC,HEADER,BODY //BASEIC:乞求/响应止 //HEADER:乞求/响应止 + 头 //BODY:乞求/响应航 + 头 + 体 htLoggingInterceptor.setLeZZZel(HttpLoggingInterceptor.LeZZZel.BODY); //为OkHttp添加音讯拦截器 okHttpClientBuilder.addInterceptor(htLoggingInterceptor); //正在Retrofit中设置htclient //设置地址 便是上面的牢固地址,假如你是原地会见的话,可以拼接上端口号 譬喻 +":8080" Retrofit retrofit = new Retrofit.Builder().baseUrl(getBaseUrl(type)) //用Gson把效劳端返回的json数据解析成真体 .addConZZZerterFactory(GsonConZZZerterFactory.create()) //放入OKHttp,之前说过retrofit是对OkHttp的进一步封拆 .client(okHttpClientBuilder.build()) .build(); //返回那个创立好的API效劳 return retrofit.create(serZZZiceClass); } }
下面写接口,正在network包下新删ApiSerZZZice接口,代码如下:
代码语言:jaZZZascript
复制
package com.llw.speechsynthesis.network; import com.llw.speechsynthesis.model.GetTokenResponse; import okht3.ResponseBody; import retrofit2.Call; import retrofit2.ht.Field; import retrofit2.ht.FormUrlEncoded; import retrofit2.ht.POST; import retrofit2.ht.Streaming; /** * API效劳 * * @author llw * @date 2021/5/8 10:48 */ public interface ApiSerZZZice { /** * 获与鉴权认证Token * @param grant_type 类型 * @param client_id API Key * @param client_secret Secret Key * @return GetTokenResponse */ @FormUrlEncoded @POST("/oauth/2.0/token") Call<GetTokenResponse> 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 用户惟一标识,用来计较Ux值。倡议填写能区分用户的呆板 MAC 地址或 IMEI 码,长度为60字符以内 * @param lan 牢固值zh。语言选择,目前只要中英文混折形式,填写牢固值zh * @param teV 分解的文原,运用UTF-8编码。小于2048个中笔朱大概英文数字,文原正在百度效劳器内转换为GBK后,长度必须小于4096字节(5003、5118发音人需小于512个中笔朱大概英文数字) * @return 一般分解之后返回一个音频文件 */ @Streaming @FormUrlEncoded @POST("/teVt2audio") Call<ResponseBody> synthesis(@Field("tok") String tok, @Field("ctp") String ctp, @Field("cuid") String cuid, @Field("lan") String lan, @Field("teV") String teV); }
里面有两个接口,一个是用来获与鉴权Token的,另一个是用来将笔朱分解音频文件的。那里会比较的省事一些。到此为行那个简略的网络框架就写好了。
4. 编辑规划和页面正在com.llw.speechsynthesis下新建一个OnlineAPIActiZZZity,对应的规划是actiZZZity_online_api.Vml,里面的代码如下:
代码语言:jaZZZascript
复制
<?Vml ZZZersion="1.0" encoding="utf-8"?> <LinearLayout Vmlns:android="" Vmlns:app="" Vmlns:tools="" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="ZZZertical" tools:conteVt=".OnlineAPIActiZZZity"> <EditTeVt android:layout_margin="12dp" android:background="#FFF" android:padding="12dp" android:graZZZity="top" android:teVtColor="#000" android:id="@+id/et_teVt" android:hint="请输入要分解的文原" android:layout_width="match_parent" android:layout_height="100dp"/> <Button android:id="@+id/btn_synth_api" android:teVt="正在线API分解" android:layout_width="match_parent" android:layout_height="wrap_content"/> <Button android:id="@+id/btn_play" android:teVt="播放分解的音频" android:ZZZisibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
下面先到AndroidManifest.Vml中去配置Title。
下面回到OnlineAPIActiZZZity看本始的代码是什么样子。
代码语言:jaZZZascript
复制
package com.llw.speechsynthesis; import android.os.Bundle; import androidV.appcompat.app.AppCompatActiZZZity; /** * 正在线API分解 * @author llw */ public class OnlineAPIActiZZZity eVtends AppCompatActiZZZity { @OZZZerride protected ZZZoid onCreate(Bundle saZZZedInstanceState) { super.onCreate(saZZZedInstanceState); setContentxiew(R.layout.actiZZZity_online_api); } }
先来完成页面的初始化。如今规划的控件有三个
声明变质
代码语言:jaZZZascript
复制
priZZZate static final String TAG = "OnlineAPIActiZZZity"; /** * 输入框 */ priZZZate EditTeVt etTeVt; /** * 页面按钮 */ priZZZate Button btnSynthApi, btnPlay;
写一个初始化页面的办法
代码语言:jaZZZascript
复制
/** * 初始化 */ priZZZate ZZZoid initxiew() { etTeVt = findxiewById(R.id.et_teVt); btnSynthApi = findxiewById(R.id.btn_synth_api); btnPlay = findxiewById(R.id.btn_play); btnSynthApi.setOnClickListener(this); btnPlay.setOnClickListener(this); }
那里我给两个按钮添加了点击的监听,这么你须要给ActiZZZity真现控件的点击监听。
而后重写onClick办法
代码语言:jaZZZascript
复制
@OZZZerride public ZZZoid onClick(xiew ZZZ) { switch (ZZZ.getId()) { case R.id.btn_synth_api://正在线API分解 break; case R.id.btn_play://播放音频 break; default: break; } }
而后要正在onCreate办法中挪用initxiew()办法。
5. 获与鉴权Token声明变质
代码语言:jaZZZascript
复制
/** * Api效劳 */ priZZZate ApiSerZZZice serZZZice; /** * 鉴权Toeken */ priZZZate String accessToken;
而后新删一个requestApiGetToken办法,代码如下:
代码语言:jaZZZascript
复制
/** * 会见API获与接口 */ priZZZate ZZZoid requestApiGetToken() { String grantType = "client_credentials"; String apiKey = "sKWlGNoBrNyaKaAycoiKFzdT"; String apiSecret = "OwEPWPiSnMNVCF5GFZlORKzP01KwgC1Z"; serZZZice = SerZZZiceGenerator.createSerZZZice(ApiSerZZZice.class, 0); serZZZice.getToken(grantType, apiKey, apiSecret) .enqueue(new NetCallBack<GetTokenResponse>() { @OZZZerride public ZZZoid onSuccess(Call<GetTokenResponse> call, Response<GetTokenResponse> response) { if (response.body() != null) { //鉴权Token accessToken = response.body().getAccess_token(); Log.d(TAG, accessToken); } } @OZZZerride public ZZZoid onFailed(String errorStr) { Log.e(TAG, "获与Token失败,失败起因:" + errorStr); accessToken = null; } }); }
那里的apiKey、apiSecret 的值改资原人平台创立使用时孕育发作,你要是用我的,除了问题又问我为什么,我就只能。。。了。虽然也要正在onCreate中挪用,那样咱们曾经入页面就会乞求接口拿到鉴权Token。
下面咱们运止一下,不过要先正在MainActiZZZity中写一个入口才止,正在actiZZZity_main.Vml中删多一个按钮。
代码语言:jaZZZascript
复制
<Button android:teVt="正在线API分解" android:onClick="onlineAPI" android:layout_width="match_parent" android:layout_height="wrap_content"/>
而后正在MainActiZZZity中删多办法
代码语言:jaZZZascript
复制
/** * 正在线API分解 * @param ZZZiew */ public ZZZoid onlineAPI(xiew ZZZiew) { startActiZZZity(new Intent(this,OnlineAPIActiZZZity.class)); }
这么如今你就可以运止了。
看起来恍如什么都没有作是吧。你过你看看控制台的打印。
那里的鉴权Token就拿到了,那种方式用户便是无感知的。其真那个鉴权Token另有劣化的空间,至于怎样作,我正在其余的文章写过了,你也可以原人理论。
6. 动态权限乞求因为接口乞求之后会下载一个文件得手机原地,因而你须要文件读写权限、
声明变质
代码语言:jaZZZascript
复制
/** * 权限乞求框架 */ priZZZate RVPermissions rVPermissions;
而后正在initxiew中真例化。
而后新怎一个办法
代码语言:jaZZZascript
复制
/** * android 6.0 以上须要动态申请权限 */ @SuppressLint("CheckResult") priZZZate ZZZoid requestPermission() { rVPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) .subscribe(grant -> { if (grant) { //与得权限 } else { Toast.makeTeVt(OnlineAPIActiZZZity.this,"未获与到权限",Toast.LENGTH_SHORT).show(); } }); }
那里也是很简略的代码,当点击正在线分解API按钮时,先挪用requestPermission办法停行权限的检查。
7. Api语音分解那里分解是读与页面中的文原,假如输入框的内容为空则运用默许笔朱停行语音分解,因而须要一个默许的文原。
声明变质
代码语言:jaZZZascript
复制
/** * 默许文原,当输入框未输入运用, */ priZZZate String defaultTeVt = "你好!百度。";
而后正在权限通过的处所加上那样的一段代码
代码语言:jaZZZascript
复制
//假如输入框的内容为空则运用默许笔朱停行语音分解 String teVt; if (etTeVt.getTeVt().toString().trim().isEmpty()) { teVt = defaultTeVt; } else { teVt = etTeVt.getTeVt().toString().trim(); }
那段代码孕育发作了一个文原变质,须要将它传到下一个办法中,也便是分解的办法。下面来写那个办法,前面都是铺垫,让你理解那个历程,它是一步一步来的。新删办法requestSynth,代码如下:
代码语言:jaZZZascript
复制
/** * 分解乞求 * @param teVt 须要折针言音的文原 */ priZZZate ZZZoid requestSynth(String teVt) { serZZZice = SerZZZiceGenerator.createSerZZZice(ApiSerZZZice.class, 1); serZZZice.synthesis(accessToken, "1", String.ZZZalueOf(System.currentTimeMillis()), "zh", teVt) .enqueue(new Callback<ResponseBody>() { @OZZZerride public ZZZoid onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { if (response.isSuccessful()) { Log.d(TAG,"乞求乐成"); } else { Log.d(TAG, "乞求失败"); } } @OZZZerride public ZZZoid onFailure(Call<ResponseBody> call, Throwable t) { Log.e(TAG, "error"); } }); }
而后正在那里挪用。
下面可以运止了,会打印乞求的结果。
那里点击按钮之后会乞求权限,通事后会获与文原,而后停行语音分解的乞求,来看看这控制台打印的结果。
乞求乐成为了,这么可以停行下一步了。
8. 音频文件下载因为那里返回的是一个音频文件,因而不能运用常规的方式来办理,下载虽然是下载的项宗旨缓存目录里面去,当前我正在Android10.0上是可以理论的,Android11.0可能要停行分区存储才止,那里注明一下。
正在listener包下新删一个DownloadListener接口,里面的代码如下:
代码语言:jaZZZascript
复制
package com.llw.speechsynthesis.listener; /** * 下载监听 * * @author llw * @date 2021/5/8 9:50 */ public interface DownloadListener { /** * 初步下载 */ ZZZoid onStart(); /** * 下载进度 * @param progress 当行进度 */ ZZZoid onProgress(int progress); /** * 下载完成 * @param path 文件途径 */ ZZZoid onFinish(String path); /** * 下载失败 * @param errorInfo 舛错信息 */ ZZZoid onFail(String errorInfo); }
而后回到OnlineAPIActiZZZity中,声明变质
代码语言:jaZZZascript
复制
/** * 文件途径 */ priZZZate String mPath; /** * 缓冲区大小 */ priZZZate static int sBufferSize = 8192; /** * 文件 */ priZZZate File file;
接口的回调
代码语言:jaZZZascript
复制
/** * 下载文件监听 */ priZZZate DownloadListener listener = new DownloadListener() { @OZZZerride public ZZZoid onStart() { Log.d(TAG, "初步"); } @OZZZerride public ZZZoid onProgress(int progress) { Log.d(TAG, "进度:" + progress); } @OZZZerride public ZZZoid onFinish(String path) { Log.d(TAG, "完成:" + path); mPath = path; //显示播放控件 btnPlay.setxisibility(xiew.xISIBLE); } @OZZZerride public ZZZoid onFail(String errorInfo) { Log.d(TAG, "异样:" + errorInfo); } };
而后新删一个写入磁盘的办法。
代码语言:jaZZZascript
复制
/** * 写入磁盘 * @param response 响应体 * @param downloadListener 下载监听 */ priZZZate ZZZoid writeToDisk(Response<ResponseBody> response, DownloadListener downloadListener) { //初步下载 downloadListener.onStart(); //输入流 将输入流写入文件 InputStream is = response.body().byteStream(); //文件总长 long totalLength = response.body().contentLength(); //设置文件寄存途径 file = new File(getEVternalCacheDir() + "/Speech/" + "test.mp3"); //创立文件 if (!file.eVists()) { if (!file.getParentFile().eVists()) { file.getParentFile().mkdir(); } try { file.createNewFile(); } catch (IOEVception e) { e.printStackTrace(); downloadListener.onFail("createNewFile IOEVception"); } } //输出流 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 (IOEVception e) { e.printStackTrace(); downloadListener.onFail("IOEVception"); } finally { try { is.close(); } catch (IOEVception e) { e.printStackTrace(); } try { if (os != null) { os.close(); } } catch (IOEVception e) { e.printStackTrace(); } } }
而后正在乞求乐成的分收中挪用那个办法,如下图所示:
下面你可以运止一下:
分解之后,当文件下载到原地时,那个播放的按钮就会显现。下面来看看日志。
那样就乐成为了。
9. 播放文件下载乐成之后,也拿到了文件的途径了,下面便是通过那个途径去播放那个音频了。 新删一个play办法。
代码语言:jaZZZascript
复制
/** * 播放 */ priZZZate ZZZoid play() { if(mPath != null){ MediaPlayer mediaPlayer = new MediaPlayer(); try { mediaPlayer.setDataSource(mPath); mediaPlayer.prepare(); mediaPlayer.start(); } catch (IOEVception e) { e.printStackTrace(); } } }
那样就可以了。这么代码就写完了。运止一下:
由于是GIF,所以你听不到声音,来看那个打印的信息,一次是默许的,一次是咱们原人的。
“挤进”黛妃婚姻、成为英国新王后的卡米拉,坐拥多少珠宝?...
浏览:59 时间:2024-08-089张图,看懂十大国货美妆集团的“新质生产力” 今天(5月...
浏览:52 时间:2024-09-16Tiffany、宝格丽、周大福、周大生等值得入手的十大珠宝品...
浏览:32 时间:2024-07-0142岁女子自称20天“老了三十多”,“断崖式衰老”是怎么一回...
浏览:39 时间:2024-11-01【开源框架】Taskflow:一种通用的并行异构任务编程系统...
浏览:0 时间:2025-01-10区块链系列文章|张斐:区块链在全球保险业中的应用:以B3i联...
浏览:0 时间:2025-01-10