双向流式TTS语音对话 SDK 是基于豆包端到端实时语音大模型,提供低延迟、双向流式TTS音频生成能力,可用于构建文本到语音的工具。
Android: com.bytedance.speechengine:speechengine_tob:0.0.12
iOS: pod 'SpeechEngineToB', '0.0.12'
该版本需要自行添加TTNet 依赖,否则Android 会崩溃,iOS 会编译报错。示例如下:
Android:
implementation 'com.bytedance.boringssl.so:boringssl-so:1.3.6' implementation('org.chromium.net:cronet:4.2.210.3-tob') { exclude group: 'com.bytedance.common', module: 'wschannel' } implementation 'com.bytedance.frameworks.baselib:ttnet:4.2.210.3-tob'
iOS:
pod 'TTNetworkManager', '4.2.210.20'
maven { url "https://artifact.bytedance.com/repository/Volcengine/" }
source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/volcengine/volcengine-specs.git' # TTNet pod 'TTNetworkManager', '{LATEST_VERSION}' # Speech pod 'SpeechEngineToB', '{LATEST_VERSION}'
类别 | 兼容范围 |
|---|---|
系统 | 最低支持 iOS 12.0 |
架构 | armv7,arm64,x86_64 |
网络 | 支持移动数据与 WiFi 两种网络环境 |
创建语音对话 SDK 引擎实例前调用,完成网络环境等相关依赖配置。APP 生命周期内仅需执行一次。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return [SpeechEngine prepareEnvironment]; }
语音对话 SDK 通过如下方式获取相关实例。
//创建实例 self.engine = [[SpeechEngine alloc] init]; //添加引擎代理,需要实现回调方法 [self.engine createEngineWithDelegate:self];
其中 APPID 、TOKEN 获取方式参考 控制台使用FAQ-Q1 ,RESOURCE ID 设置为 volc.service_type.10029,URI设置为/api/v3/tts/bidirection。
//【必需配置】Engine Name [self.curEngine setStringParam:SE_BITTS_ENGINE forKey:SE_PARAMS_KEY_ENGINE_NAME_STRING]; //【可选配置】本地日志文件路径,SDK会在该路径文件夹下生成名为 speech_sdk.log 的日志文件,开发时设置,线上关闭。 [self.engine setStringParam:@"已存在的文件夹路径,或者空字符串" forKey:SE_PARAMS_KEY_DEBUG_PATH_STRING]; //【可选配置】日志级别,开发时设置为 TRACE(最低级别),线上设置 WARN; [self.engine setStringParam:SE_LOG_LEVEL_TRACE forKey:SE_PARAMS_KEY_LOG_LEVEL_STRING]; //【可选配置】自定义请求Header [self.curEngine setStringParam:headerStr forKey:SE_PARAMS_KEY_REQUEST_HEADERS_STRING]; //【可选配置】是否使用 SDK 内置播放器播放合成出的音频,默认为 true [self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_PLAYER] forKey:SE_PARAMS_KEY_TTS_ENABLE_PLAYER_BOOL]; //【可选配置】是否令 SDK 通过回调返回播放器播放的pcm音频数据,默认不返回。 // 开启后,SDK 会流式返回音频,收到 SEPlayerStartPlayAudio SEPlayerFinishPlayAudio表示开始播放当前句对应音频 //收到SEPlayerAudioData表示流式的pcm音频数据 [self.curEngine setBoolParam:[self.settings getBool:SETTING_ENABLE_PLAYER_AUDIO_CALL_BACK] ? SETtsDataCallbackModeAll : SETtsDataCallbackModeNone forKey:SE_PARAMS_KEY_ENABLE_PLAYER_AUDIO_CALLBACK_BOOL]; //【可选配置】是否将合成出的音频保存到设备上,为 true 时需要正确配置 PARAMS_KEY_TTS_AUDIO_PATH_STRING 才会生效 [self.curEngine setBoolParam:[self.settings getBool:SETTING_TTS_ENABLE_DUMP] //【可选配置】TTS播放文件保存路径,如不为空字符串,则SDK会将播放器音频保存到该路径下,文件格式为 .wav [self.curEngine setStringParam:self.debugPath forKey:SE_PARAMS_KEY_TTS_AUDIO_PATH_STRING]; //【必需配置】在线合成鉴权相关:Appid [self.curEngine setStringParam:self.ttsAppId forKey:SE_PARAMS_KEY_APP_ID_STRING]; //【必需配置】语音合成服务域名 [self.curEngine setStringParam:ttsAddress forKey:SE_PARAMS_KEY_TTS_ADDRESS_STRING]; //【必需配置】语音合成服务Uri [self.curEngine setStringParam:ttsUri forKey:SE_PARAMS_KEY_TTS_URI_STRING]; //【必需配置】语音合成服务资源id [self.curEngine setStringParam:resourceId forKey: SE_PARAMS_KEY_RESOURCE_ID_STRING]; //【可选配置】TTS连接超时时间 [self.curEngine setIntParam:10000 forKey:SE_PARAMS_KEY_TTS_CONN_TIMEOUT_INT]; //【必需配置】StarSession时的payload,指定音频生成相关参数,不包含待合成的文本 [self.curEngine setStringParam:startPayload forKey:SE_PARAMS_KEY_START_ENGINE_PAYLOAD_STRING];
初始化引擎对象并设置回调监听。
// 初始化引擎实例 SEEngineErrorCode ret = [self.engine initEngine]; if (ret != SENoError) { NSLog(@"初始化失败,返回值: %d", ret); return; }
语音对话 SDK 通过发送指令接口 sendDirective 触发各种操作,需要注意不建议在 SDK 的回调线程中调用该接口。
启动引擎的传参为StartSession命令的参数,详细参数可以见双向流式TTS的Event事件。
// 注意这里先调用同步停止,避免SDK内部异步线程带来的问题 [self.engine sendDirective:SEDirectiveSyncStopEngine]; // 启动BiTTS引擎 SEEngineErrorCode ret = [self.engine sendDirective:SEDirectiveStartEngine data: startSessionPayload]; if (ret != SENoError) { NSLog(@"Send directive say hello failed: %d", ret); return; }
startSessionPayload参数示例如下,包含了音色、音频生成参数。
"{\"user\":{\"uid\":\"YOURUID\"},\"req_params\":{\"speaker\":\"zh_female_roumeinvyou_emo_v2_mars_bigtts\",\"audio_params\":{\"emotion\":\"excited\",\"loudness_rate\":50}}}"
说明
注意:启动BiTTS引擎只完成了建连操作,没有发起StartSession,在开始合成前需要先调用[self.curEngine sendDirective:SEDirectiveEventStartSession data:@""];
双向流式TTS支持在一个链接中多次调用StartSession和FinishSession。每调用一次表示开始合成服务和结束合成服务。
调用StartSession时data字段有如下2种设置方式
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveEventStartSession data:@""]; //或者传入更新后的新参数 //SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveEventStartSession data:startSessionPayload]; if (ret != SENoError) { NSLog(@"Send directive say Start Session failed: %d", ret); return; }
如果没有文本了,或者需要强制结束时可以调用FinishSession让服务端基于已收到的文本完成音频合成并返回。
说明
注意:如果传输文本最后不包含标点符号,那么服务端会等待后续文本到来才会触发合成。超时4秒没有收到后续文本也会触发合成。如果期望服务端立马开始合成则需要调用FinishSession。
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveEventFinishSession data:""]; if (ret != SENoError) { NSLog(@"Send directive Finish Session failed: %d", ret); return; }
文本内容需要拼装车json格式的字符串传入。如果有其他参数可以透传给服务端。
NSString* taskRequestJson = [NSString stringWithFormat: @"{\"req_params\":{\"text\":\"%@\"}}", text]; SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveEventTaskRequest data:taskRequestJson]; if (ret != SENoError) { NSLog(@"Send directive Task Request failed: %d", ret); return; }
说明
可以调用多次SEDirectiveEventTaskRequest向服务端发送待合成的文本内容。服务端会切句逐个返回。如果需要立即返回音频内容,可以在发送SEDirectiveEventTaskRequest前后调用StartSession和FinishSession确保音频马上返回。
如果要取消当前合成服务,期望服务端不再返回音频。则调用CancleSession
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveEventCancleSession data:""]; if (ret != SENoError) { NSLog(@"Send directive Finish Session failed: %d", ret); return; }
可以在播放音频时发送指令SEDirectivePausePlayer*,*暂停tts音频播放。
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectivePausePlayer]; NSLog(@"Pause playback status: %d", ret);
可以在播放音频时发送指令SEDirectiveResumePlayer*,*恢复tts音频播放
SEEngineErrorCode ret = [self.curEngine sendDirective:SEDirectiveResumePlayer]; NSLog(@"Resume playback status: %d", ret);
// 会等待回调函数执行完成,不可在回调线程中执行。 [self.engine sendDirective:SEDirectiveSyncStopEngine];
启动引擎后,SDK会不断回调消息。回调的消息内容,请参考双向流式TTS的Event事件说明。
- (void)onMessageWithType:(SEMessageType)type andData:(NSData *)data { NSLog(@"Message Type: %d.", type); NSString *strData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; switch (type) { case SEEngineStart: // Callback: 引擎启动成功回调 NSLog(@"Callback: 引擎启动成功: %@", strData); break; case SEEngineStop: // Callback: 引擎关闭回调,启动引擎成功后,此消息必定发生且为最后一个回调消息 NSLog(@"Callback: 引擎关闭: %@", strData); break; case SEEngineError: // Callback: 错误信息回调 NSLog(@"Callback: 错误信息: %d, data: %@", type, strData); break; case SEEventConnectionStarted: NSLog(@"Callback: SEEventConnectionStarted"); break; case SEEventConnectionFailed: NSLog(@"Callback: SEEventConnectionFailed"); break; case SEEventConnectionFinished: NSLog(@"Callback: SEEventConnectionFinished"); break; case SEEventSessionStarted: NSLog(@"Callback: SEEventSessionStarted"); break; case SEEventSessionCanceled: NSLog(@"Callback: SEEventSessionCanceled"); break; case SEEventSessionFinished: NSLog(@"Callback: SEEventSessionFinished"); break; case SEEventSessionFailed: NSLog(@"Callback: SEEventSessionFailed"); break; case SEEventTTSSentenceStart: NSLog(@"Callback: 合成开始 SentenceStart: %@", data); break; case SEEventTTSSentenceEnd: NSLog(@"Callback: 合成结束 SentenceEnd: %@", data); case SEEventTTSResponse: NSLog(@"Callback: 收到合成音频 TTSResponse: %@", data); case SEEventTTSEnded: NSLog(@"Callback: TTSEnd: %@", data); case SEPlayerAudioData: NSLog(@"Callback: 播放的pcm音频: %@", data); break; case SEPlayerStartPlayAudio: NSLog(@"Callback: 开始播放当前句的TTS音频"); break; case SEPlayerFinishPlayAudio: NSLog(@"Callback: 结束播放当前句的TTS音频"); break; default: break; } }
当不再需要语音对话后,建议对引擎实例进行销毁,释放内存资源。
// 内部会执行SYNC_STOP,不可在回调线程中执行。 [self.engine destroyEngine]; self.engine = nil;
pod install --repo-update:其中 APPID 、TOKEN 获取方式参考 控制台使用FAQ-Q1
RESOURCE ID 设置为 volc.service_type.10029。
const NSString* SDEF_BITTS_DEFAULT_RESOURCE_ID = @"volc.service_type.10029"; const NSString* SDEF_BITTS_DEFAULT_URI = @"/api/v3/tts/bidirection";
一段完整的TTS合成日志如下所示
Callback: SEEventSessionStarted Callback: 合成开始 SentenceStart: {length = 87, bytes = 0x7b227068 6f6e656d 6573223a 5b5d2c22 ... 72647322 3a5b5d7d } Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 开始播放TTS音频 Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 收到合成音频 TTSResponse: {length = 4, bytes = 0x4f676753} Callback: TTSEnd: {length = 4, bytes = 0x4f676753} Callback: 合成结束 SentenceEnd: {length = 87, bytes = 0x7b227068 6f6e656d 6573223a 5b5d2c22 ... 72647322 3a5b5d7d } Callback: 收到合成音频 TTSResponse: {length = 87, bytes = 0x7b227068 6f6e656d 6573223a 5b5d2c22 ... 72647322 3a5b5d7d } Callback: TTSEnd: {length = 87, bytes = 0x7b227068 6f6e656d 6573223a 5b5d2c22 ... 72647322 3a5b5d7d } Callback: SEEventSessionFinished Callback: 结束播放TTS音频