创建声音复刻 SDK 引擎实例前调用,完成网络环境等相关依赖配置。本方法每个App进程生命周期内仅需调用一次。
SpeechEngineGenerator.PrepareEnvironment(getApplicationContext(), getApplication());
声音复刻 SDK 如下方式获取相关实例。
SpeechEngine engine = SpeechEngineGenerator.getInstance(); engine.createEngine();
对于声音复刻 SDK 所有流程,需配置如下通用参数:
// 声音复刻引擎 engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_ENGINE_NAME_STRING, SpeechEngineDefines.VOICECLONE_ENGINE);
为便于开发者集成调试,有如下建议:
日志级别,开发时设置为 TRACE
,线上设置WARN
;
调试路径,声音复刻 SDK 会在该路径下生成名为 speech_sdk.log
的日志文件,开发时设置,线上关闭。
engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_LOG_LEVEL_STRING, SpeechEngineDefines.LOG_LEVEL_WARN); engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_DEBUG_PATH_STRING, "{DEBUG PATH}");
如果需要一个用户持有多个音色,需要业务方自行组织用户子ID格式,例如通过“用户ID + 序号/时间戳”来生成用户子ID,从而确保音色的唯一性。
engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_UID_STRING, "{YOUR UID}");
需要申请 Appid
和 Token
,配置时 Token
需要添加固定前缀 Bearer;
。
engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_APP_ID_STRING, "{YOUR APPID}"); engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_APP_TOKEN_STRING, "Bearer;{YOUR TOKEN}");
发起声音复刻请求,需要配置请求地址
参数。
engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_ADDRESS_STRING, "https://openspeech.bytedance.com"); engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_STREAM_ADDRESS_STRING, "wss://openspeech.bytedance.com");
SDK 还支持调整建连、接收超时,单位:毫秒。
// 如无特殊需要,建议使用默认值 engine.setOptionInt(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_CONN_TIMEOUT_INT, 12000); engine.setOptionInt(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_RECV_TIMEOUT_INT, 8000);
语音识别 SDK 支持以录音机、原始音频流或音频文件作为输入,配置值分别为:
engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_RECORDER_TYPE_STRING, SpeechEngineDefines.RECORDER_TYPE_RECORDER);
engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_RECORDER_TYPE_STRING, SpeechEngineDefines.RECORDER_TYPE_FILE); engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_RECORDER_FILE_STRING, "../data/voiceclone_rec_file.pcm");
engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_RECORDER_TYPE_STRING, SpeechEngineDefines.RECORDER_TYPE_STREAM); // ... // 启动某复刻流程 // ... // 流式输入音频数据 while (nread > 0) { int ret = engine.feedAudio(buffer, nread); if (ret != SpeechEngineDefines.ERR_NO_ERROR) { Log.e(SpeechDefines.TAG, "Feed audio Failed: " + ret); } } // 音频输入完成 int ret = engine.sendDirective(SpeechEngineDefines.DIRECTIVE_FINISH_TALKING, ""); if (ret != SpeechEngineDefines.ERR_NO_ERROR) { Log.e(SpeechDefines.TAG, "Finish talking Failed: " + ret); }
对于复刻流程中可能出现的录音操作,是否将录到的音频以wav格式写录音文件。
// 是否开启写录音文件 engine.setOptionBoolean(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_ENABLE_DUMP_BOOL, true); // 写录音文件路径 engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_REC_PATH_STRING, "{REC FILE PATH}");
参数配置完成后,调用 初始化接口
,完成引擎实例的初始化,初始化后配置 回调监听器
。
int ret = engine.initEngine(); if (ret != SpeechEngineDefines.ERR_NO_ERROR) { String errMessage = "Init Engine Failed: " + ret; Log.e(SpeechDefines.TAG, errMessage); } engine.setListener(this);
// 设置用户性别 engine.setOptionBoolean(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_GENDER_BOOL, false);
int ret = engine.sendDirective(SpeechEngineDefines.DIRECTIVE_VOICECLONE_GET_TASK, ""); if (ret != SpeechEngineDefines.ERR_NO_ERROR) { Log.e(SpeechDefines.TAG, "Fail to get task: " + ret); }
@Override public void onSpeechMessage(int type, byte[] bytes, int len) { String data = new String(bytes); switch (type) { case SpeechEngineDefines.MESSAGE_TYPE_VOICECLONE_GET_TASK_RESULT: Log.i(SpeechDefines.TAG, "Get task: " + data); break; } }
其中data为任务信息JSON字符串,JSON格式如下:
[ { "task_id":10, // int32, 音色复刻任务ID,任务类型标识 "progress":1, // int32, 当前用户已完成进度 "total_text":20, // int32, 任务文本数 "texts":["文本","文本","文本"] // array(string), 任务文本数组,每项为所需朗读的文本字符串 }, { "task_id":11, "progress":1, "total_text":20, "texts":["文本","文本","文本"] } ]
// 设置当前执行的任务ID engine.setOptionInt(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_TASKID_INT, 10);
int ret = engine.sendDirective(SpeechEngineDefines.DIRECTIVE_VOICECLONE_CHECK_ENV, ""); if (ret != SpeechEngineDefines.ERR_NO_ERROR) { Log.e(SpeechDefines.TAG, "Fail to check env: " + ret); }
@Override public void onSpeechMessage(int type, byte[] bytes, int len) { String data = new String(bytes); switch (type) { case SpeechEngineDefines.MESSAGE_TYPE_VOICECLONE_CHECK_ENV_RESULT: Log.i(SpeechDefines.TAG, "Check env: " + data); break; } }
其中data为检测结果JSON字符串,JSON格式如下:
{ "status":0, // int32, 环境检测状态 "noise":1, // double(float64), 噪音分贝 }
其中status:(等于0 -- 通过;小于0 -- 未通过;大于0 -- 检测中)
2:检测中,当前时间区间噪音较大
1:检测中,当前时间区间信噪比检测正常
0:检测通过
-1:检测失败,未知错误
-2:检测未通过,噪音较大
复刻任务ID: 设置执行的复刻任务ID。
文本序号: 当前朗读的文本在整个任务中的序号,0为第一句。
文本内容: 当前朗读的任务文本内容。
// 设置当前执行的复刻任务ID engine.setOptionInt(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_TASKID_INT, 10); // 设置任务文本序号 engine.setOptionInt(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_TEXT_SEQ_INT, 0); // 设置任务文本内容 engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_TEXT_STRING, "文本");
int ret = engine.sendDirective(SpeechEngineDefines.DIRECTIVE_VOICECLONE_RECORD_VOICE, ""); if (ret != SpeechEngineDefines.ERR_NO_ERROR) { Log.e(SpeechDefines.TAG, "Fail to record voice: " + ret); }
// 音频输入完成,等待最终处理结果 engine.sendDirective(SpeechEngineDefines.DIRECTIVE_FINISH_TALKING, "");
@Override public void onSpeechMessage(int type, byte[] bytes, int len) { String data = new String(bytes); switch (type) { case SpeechEngineDefines.MESSAGE_TYPE_VOICECLONE_RECORD_VOICE_RESULT: Log.i(SpeechDefines.TAG, "Record voice: " + data); break; } }
其中data为检测结果JSON字符串,JSON格式如下:
{ "status":0, // int32, 录音检测结果 "wrong_index": [ // array(int32), 检测未通过的word索引,假设有文本 "一二三四五六" 而wrong_index:[2] 的意思是 "三"字没有通过检测 2, 3 ] }
其中status: (等于0 -- 通过;小于0 -- 未通过;大于0 -- 检测中)
0: 检测通过
-1: 检测失败,未知错误
-2: 噪音太大
-3: 声音太小
-4: 声音太大
-5: 信噪比过低
-6: 识别结果中有错字
-7: 识别结果中有多读
-8: 识别结果中有漏读
-9: 录音进度冲突,如重复录制当前位置之前的文本
注:多读、漏读时wrong_index为空,且问题优先级:多读 > 漏读 > 错字,即有多读时会忽略漏读错字等问题。
// 设置当前执行的任务ID engine.setOptionInt(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_TASKID_INT, 10);
int ret = engine.sendDirective(SpeechEngineDefines.DIRECTIVE_VOICECLONE_SUBMIT_TASK, ""); if (ret != SpeechEngineDefines.ERR_NO_ERROR) { Log.e(SpeechDefines.TAG, "Fail to submit: " + ret); }
@Override public void onSpeechMessage(int type, byte[] bytes, int len) { String data = new String(bytes); switch (type) { case SpeechEngineDefines.MESSAGE_TYPE_VOICECLONE_SUBMIT_TASK_RESULT: Log.i(SpeechDefines.TAG, "Submit: " + data); break; } }
其中data 为JSON字符串, JSON格式如下:
{ "voice_type":"abc", // string, 音色名,用于训练完成后语音合成时做传入参数 }
// 设置需要查询的用户子ID engine.setOptionString(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_QUERY_UIDS_STRING, "uid1;uid2;uid3");
int ret = engine.sendDirective(SpeechEngineDefines.DIRECTIVE_VOICECLONE_QUERY_STATUS, ""); if (ret != SpeechEngineDefines.ERR_NO_ERROR) { Log.e(SpeechDefines.TAG, "Fail to query status: " + ret); }
@Override public void onSpeechMessage(int type, byte[] bytes, int len) { String data = new String(bytes); switch (type) { case SpeechEngineDefines.MESSAGE_TYPE_VOICECLONE_QUERY_STATUS_RESULT: Log.i(SpeechDefines.TAG, "Query status: " + data); break; } }
其中data为查询结果JSON字符串,JSON格式如下:
[ { "status":0, // int32, 状态码 "uid":"388808087185088", // string, 用户子ID "task_id":1, // int32, 任务ID "appid":"test", "extra":{ "voice_type": "xxx", // string, 音色名,用于训练完成后语音合成时做传入参数 "vc_styles":[ {"name":"通用中文","value":"default"}, {"name":"通用美式英文","value":"en"} ], // 音色支持的风格名和风格值,其中风格值可以传入TTS引擎合成使用。 "record_url":["",""] // 录音音频地址 } }, { "status":1, "uid":"388808087185088", "appid":"test", "task_id":1, "extra":{} }, { "uid":"388808087185088", "appid":"test", "task_id":1, "status":2, "extra":{ "queueing_count":10, // 前面排队中的人 "queueing_cost":50, // 排队耗时,单位为分钟 } }, { "uid":"388808087185088", "appid":"test", "task_id":1, "status":3, "extra":{ "training_cost":50, // 训练平均耗时,单位为分钟 } }, { "uid":"388808087185088", "appid":"test", "task_id":1, "status":4, "extra":{ "loading_cost":2, // 加载平均耗时,单位为分钟 } } ]
其中status: (等于0 -- 通过;小于0 -- 训练失败;大于0 -- 训练中)
-2: 任务不存在
-1: 训练失败
0: 音色可用
1: 录制中
2: 排队中
3: 训练中
4: 音色加载中
// 设置当前执行的任务ID engine.setOptionInt(SpeechEngineDefines.PARAMS_KEY_VOICECLONE_TASKID_INT, 10);
int ret = engine.sendDirective(SpeechEngineDefines.DIRECTIVE_VOICECLONE_DELETE_DATA, ""); if (ret != SpeechEngineDefines.ERR_NO_ERROR) { Log.e(SpeechDefines.TAG, "Fail to delete data: " + ret); }
@Override public void onSpeechMessage(int type, byte[] bytes, int len) { String data = new String(bytes); switch (type) { case SpeechEngineDefines.MESSAGE_TYPE_VOICECLONE_DELETE_DATA_RESULT: Log.i(SpeechDefines.TAG, "Delete data"); break; } }
接收到该回调即为删除成功,其中data为空字符串。
一个引擎实例在一时间只能执行某一个流程,可以通过两种方式立刻停止正在执行的流程而不需要处理结果。
需要在收到 SpeechEngineDefines.MESSAGE_TYPE_ENGINE_STOP 消息后引擎才算真正停止。
engine.sendDirective(SpeechEngineDefines.DIRECTIVE_STOP_ENGINE, "");
执行后会在当前位置等待引擎结束,再执行下一行代码。请勿直接在回调线程中执行该方法,否则可能导致死锁等待。
engine.sendDirective(SpeechEngineDefines.DIRECTIVE_SYNC_STOP_ENGINE, "");
当不再需要声音复刻功能后,建议对引擎实例进行销毁,释放内存资源。
engine.destroyEngine();