Browse Source

上传tts的优化方案

weicu
liwei1dao 1 year ago
parent
commit
98159fab8e
  1. 31
      lib/data/services/speech_impl/azure_tts_service.dart
  2. 27
      lib/data/services/speech_impl/voice_clone_tts_service.dart
  3. 27
      lib/data/services/speech_impl/volcano_tts_service.dart
  4. 9
      lib/data/services/tts_service.dart
  5. 4
      lib/modules/FTFTranslation/controllers/FTFTranslation_controller.dart
  6. 7
      lib/modules/agent/controllers/agent_controller.dart
  7. 8
      lib/modules/speech_test/controllers/speech_test_controller.dart
  8. 7
      lib/modules/translation/controllers/translation_controller.dart
  9. 11
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  10. 3
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift
  11. 13
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift
  12. 50
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift
  13. 10
      local_plugins/speech/ios/speech/Sources/speech/ITtsService.swift

31
lib/data/services/speech_impl/azure_tts_service.dart

@ -37,7 +37,7 @@ class AzureTtsService extends GetxService implements TtsService {
// 可观察状态
final isEnabled = true.obs;
final _isSpeaking = false.obs;
String _currsessionid = '';
// 流式文本缓冲区
String _streamBuffer = '';
@ -183,13 +183,32 @@ class AzureTtsService extends GetxService implements TtsService {
}
@override
Future<bool> speakOnce(String text) async {
Future<bool> startspeak(String sessionid) async {
if (!_isInitialized) await initialize();
if (!isEnabled.value) return false;
try {
// 开始合成,传递参数
final result = await _channel.invokeMethod('startspeak', {
'sessionid': sessionid,
});
return result == true;
} catch (e) {
Logger.error('语音合成失败: ${e.toString()}');
return false;
}
}
@override
Future<bool> speakOnce(String sessionid, String text) async {
if (!_isInitialized) await initialize();
if (!isEnabled.value || text.isEmpty) return false;
try {
// 开始合成,传递参数
final result = await _channel.invokeMethod('speakText', {
'sessionid': sessionid,
'text': text,
});
@ -201,7 +220,7 @@ class AzureTtsService extends GetxService implements TtsService {
}
@override
Future<bool> speakStream(String text) async {
Future<bool> speakStream(String sessionid, String text) async {
if (!isEnabled.value || text.isEmpty) return false;
try {
@ -256,12 +275,12 @@ class AzureTtsService extends GetxService implements TtsService {
}
@override
Future<bool> flushStream() async {
Future<bool> flushStream(String sessionid) async {
if (_textQueue.isEmpty && _streamBuffer.isEmpty) return true;
try {
// 处理缓冲区中可能的完整句子
await speakStream('');
await speakStream(sessionid, '');
// 如果缓冲区仍有剩余文本,将其添加到播放队列
if (_streamBuffer.isNotEmpty && _streamBuffer.trim().isNotEmpty) {
@ -345,7 +364,7 @@ class AzureTtsService extends GetxService implements TtsService {
final text = _textQueue.first;
// 开始合成
final success = await speakOnce(text);
final success = await speakOnce(_currsessionid, text);
// 仅当合成操作失败时才从队列移除并继续处理下一项
// 成功的话会等待合成完成事件后自动处理下一项

27
lib/data/services/speech_impl/voice_clone_tts_service.dart

@ -27,6 +27,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
late ConcatenatingAudioSource _playlist;
// 当前语音
String _currsessionid = '';
final RxString _currentVoice = ''.obs;
@override
@ -215,7 +216,21 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
}
@override
Future<bool> speakOnce(String text) async {
Future<bool> startspeak(String sessionid) async {
if (!_isInitialized) await initialize();
if (!isEnabled.value) return false;
try {
_currsessionid = sessionid;
return true;
} catch (e) {
Logger.error('语音合成失败: ${e.toString()}');
return false;
}
}
@override
Future<bool> speakOnce(String sessionid, String text) async {
if (!isEnabled.value || text.isEmpty) return false;
try {
@ -236,7 +251,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
}
@override
Future<bool> speakStream(String text) async {
Future<bool> speakStream(String sessionid, String text) async {
if (!isEnabled.value || text.isEmpty) return false;
try {
@ -295,12 +310,14 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
}
@override
Future<bool> flushStream() async {
Future<bool> flushStream(
String sessionid,
) async {
if (_textQueue.isEmpty && _streamBuffer.isEmpty) return true;
try {
// 处理缓冲区中可能的完整句子
await speakStream('');
await speakStream(sessionid, '');
// 如果缓冲区仍有剩余文本,将其添加到播放队列
if (_streamBuffer.isNotEmpty && _streamBuffer.trim().isNotEmpty) {
@ -344,7 +361,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
final item = _textQueue.removeAt(0);
// 使用speakOnce播放当前项
await speakOnce(item);
await speakOnce(_currsessionid, item);
}
} catch (e) {
Logger.error('处理语音队列出错: ${e.toString()}');

27
lib/data/services/speech_impl/volcano_tts_service.dart

@ -206,7 +206,26 @@ class VolcanoTtsService extends GetxService implements TtsService {
}
@override
Future<bool> speakOnce(String text) async {
Future<bool> startspeak(String sessionid) async {
if (!await _ensureInitialized()) return false;
try {
// 单次播放
final result = await _channel.invokeMethod<bool>('startspeak', {
'sessionid': sessionid,
}) ??
false;
return result;
} catch (e) {
debugPrint('单次播放失败:$e');
_eventStreamController.add(TtsEvent.error('单次播放失败:$e'));
return false;
}
}
@override
Future<bool> speakOnce(String sessionid, String text) async {
if (!await _ensureInitialized()) return false;
try {
@ -225,7 +244,7 @@ class VolcanoTtsService extends GetxService implements TtsService {
}
@override
Future<bool> speakStream(String text) async {
Future<bool> speakStream(String sessionid, String text) async {
if (!await _ensureInitialized()) return false;
try {
@ -244,7 +263,9 @@ class VolcanoTtsService extends GetxService implements TtsService {
}
@override
Future<bool> flushStream() async {
Future<bool> flushStream(
String sessionid,
) async {
if (!await _ensureInitialized()) return false;
try {

9
lib/data/services/tts_service.dart

@ -19,14 +19,17 @@ abstract class TtsService {
/// 设置语音
Future<bool> setVoice(String voiceName);
///开始新的播放会话
Future<bool> startspeak(String sessionid);
/// 单次播放文本,完成后返回
Future<bool> speakOnce(String text);
Future<bool> speakOnce(String sessionid, String text);
/// 将文本加入播放队列,支持连续播放多段文本
Future<bool> speakStream(String text);
Future<bool> speakStream(String sessionid, String text);
/// 播放队列中剩余文本
Future<bool> flushStream();
Future<bool> flushStream(String sessionid);
/// 停止播放
Future<bool> stop();

4
lib/modules/FTFTranslation/controllers/FTFTranslation_controller.dart

@ -42,7 +42,7 @@ class FTFTranslationController extends GetxController
final sourceLanguageCode = 'zh-CN'.obs;
final targetLanguageCode = 'en-US'.obs;
final isTtsEnabled = true.obs;
var _currsessionid = '';
@override
void onInit() {
super.onInit();
@ -168,7 +168,7 @@ class FTFTranslationController extends GetxController
'en-US-AriaNeural';
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text);
await _ttsService.speakOnce(_currsessionid, text);
} catch (e) {
Logger.error('播放翻译失败: ${e.toString()}');
}

7
lib/modules/agent/controllers/agent_controller.dart

@ -949,10 +949,6 @@ class AgentController extends GetxController {
final text = textController.text.trim();
if (text.isEmpty) return;
final message = Message(isUser: true, text: text);
messages.add(message);
textController.clear();
// 使用reverse:true时通常不需要手动滚动
// 3. 调用AI服务处理
try {
@ -963,6 +959,9 @@ class AgentController extends GetxController {
// 根据TTS状态决定是否朗读
await AgentService.processTextInput(text,
speakResponse: isTtsEnabled.value);
final message = Message(isUser: true, text: text);
messages.add(message);
textController.clear();
} catch (e) {
Logger.e(TAG, '发送文本消息失败: $e');
isProcessing.value = false;

8
lib/modules/speech_test/controllers/speech_test_controller.dart

@ -53,6 +53,8 @@ class SpeechTestController extends GetxController {
'这是第三句测试语音,将在连续模式下播放完毕。'
];
var _currsessionid = '';
@override
void onInit() {
super.onInit();
@ -246,7 +248,7 @@ class SpeechTestController extends GetxController {
isProcessing.value = true;
ttsStatus.value = '准备播放...';
final result = await _ttsService.speakOnce(text);
final result = await _ttsService.speakOnce(_currsessionid, text);
if (!result) {
ttsStatus.value = '播放失败';
@ -269,7 +271,7 @@ class SpeechTestController extends GetxController {
// 为避免阻塞,使用一个句子接一个句子的方式添加到队列
for (final sentence in testSentences) {
if (!await _ttsService.speakStream(sentence)) {
if (!await _ttsService.speakStream(_currsessionid, sentence)) {
ttsStatus.value = '添加句子到队列失败';
break;
}
@ -278,7 +280,7 @@ class SpeechTestController extends GetxController {
}
// 刷新队列,确保播放完成
await _ttsService.flushStream();
await _ttsService.flushStream(_currsessionid);
} catch (e) {
ttsStatus.value = '连续播放异常: $e';
Logger.error('连续播放异常: ${e.toString()}');

7
lib/modules/translation/controllers/translation_controller.dart

@ -88,6 +88,7 @@ class TranslationController extends GetxController {
final targetLanguageCode = 'en-US'.obs;
var detectedLanguageCode = '';
var currsessionid = '';
final currentModeTitle = 'simultaneousTranslation'.obs;
final currentMode = 'simultaneous'.obs;
// 翻译历史和当前项
@ -935,7 +936,7 @@ class TranslationController extends GetxController {
final startTime = DateTime.now();
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text);
await _ttsService.speakOnce(currsessionid, text);
final endTime = DateTime.now();
// 记录TTS API调用统计 - 优化版(基于文本字符数计费)
@ -991,7 +992,7 @@ class TranslationController extends GetxController {
_languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ??
'en-US-AriaNeural';
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(item.translatedText);
await _ttsService.speakOnce(currsessionid, item.translatedText);
} catch (e) {
Logger.error('播放翻译项失败: ${e.toString()}');
}
@ -1010,7 +1011,7 @@ class TranslationController extends GetxController {
'en-US-AriaNeural';
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text);
await _ttsService.speakOnce(currsessionid, text);
} catch (e) {
Logger.error('播放文本失败: ${e.toString()}');
}

11
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -378,7 +378,7 @@ class AgentServiceImpl: NSObject {
return false
}
}
stopTts();
if isRecognizing {
stopRecognition()
}
@ -456,7 +456,7 @@ audioStream.saveAudioDataTo(data: audioData)
return true
}
func speakText(_ text: String) -> Bool {
func speakText(sessionid sessionid:String,_ text: String) -> Bool {
if !isInitialized {
sendError("服务未初始化", code: "NOT_INITIALIZED")
return false
@ -466,7 +466,7 @@ audioStream.saveAudioDataTo(data: audioData)
return false
}
return azureTtsHelper?.speakOnce(text) ?? false
return azureTtsHelper?.speakOnce(sessionid:sessionid,text) ?? false
}
func stopTts() -> Bool {
@ -591,6 +591,7 @@ audioStream.saveAudioDataTo(data: audioData)
imagePath: imagePath
)
currsessionId = UUID().uuidString
azureTtsHelper?.startspeak(sessionid: currsessionId)
os_log("设置回调并调用sendMessageStream", log: logger, type: .info)
chatApiService.setStreamCallback(currsessionId,callback)
chatApiService.sendMessageStream(currsessionId,messages: messages)
@ -1253,7 +1254,7 @@ class ChatApiStreamCallback: StreamCallback {
responseBuilder += token
if speakResponse && reply && broadcast{
try agentService.azureTtsHelper?.speakStream(token)
try agentService.azureTtsHelper?.speakStream(sessionid:sessionId,token)
// 在开始流式TTS时立即停止气泡音
}
if (reply && token != "") {
@ -1275,7 +1276,7 @@ class ChatApiStreamCallback: StreamCallback {
if speakResponse && reply && broadcast && sessionId == agentService.currsessionId{
agentService.azureTtsHelper?.flushStream()
agentService.azureTtsHelper?.flushStream(sessionid:sessionId)
}
let response = responseBuilder

3
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift

@ -86,12 +86,13 @@ public class AgentServicePlugin: NSObject, FlutterPlugin {
case "speakText":
guard let arguments = call.arguments as? [String: Any],
let sessionid = arguments["sessionid"] as? String,
let text = arguments["text"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
let success = impl.speakText(text)
let success = impl.speakText(sessionid:sessionid,text)
result(success)
case "stopTts":

13
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift

@ -480,26 +480,33 @@ private func sendAstEvent(_ event: [String: Any]) {
case "speakText":
guard let args = call.arguments as? [String: Any],
let sessionid = args["sessionid"] as? String,
let text = args["text"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
let success = azureTtsHelper.speakOnce(text)
let success = azureTtsHelper.speakOnce(sessionid:sessionid,text)
result(success)
case "speakStream":
guard let args = call.arguments as? [String: Any],
let sessionid = args["sessionid"] as? String,
let text = args["text"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
let success = azureTtsHelper.speakStream(text)
let success = azureTtsHelper.speakStream(sessionid:sessionid,text)
result(success)
case "flushStream":
let success = azureTtsHelper.flushStream()
guard let args = call.arguments as? [String: Any],
let sessionid = args["sessionid"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
let success = azureTtsHelper.flushStream(sessionid: sessionid)
result(success)
case "stopSpeaking":

50
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift

@ -16,6 +16,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
private var synthesizer: SPXSpeechSynthesizer?
private var isInitialized = false
private var speaking = false
private var sessionid = "" //语音播报任务id
// 当前配置
private var currentVoice = DEFAULT_VOICE
@ -109,10 +110,14 @@ public class AzureTtsHelper: NSObject, ITtsService {
}
}
public func startspeak(sessionid sessionid: String)-> Bool {
self.sessionid = sessionid
return true
}
/**
* 单次合成并播放
*/
public func speakOnce(_ text: String) -> Bool {
public func speakOnce(sessionid sessionid: String,_ text: String) -> Bool {
if !isInitialized {
os_log("语音合成未初始化", log: log, type: .error)
notifyEvent(eventType: .error, params: [
@ -122,6 +127,10 @@ public class AzureTtsHelper: NSObject, ITtsService {
return false
}
if (self.sessionid != sessionid){
return false
}
// 清理文本
let cleanedText = cleanTextForTTS(text)
if cleanedText.isEmpty {
@ -130,7 +139,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
// 异步处理
enqueueSynthesisTask {
self.performSynthesis(text: cleanedText)
self.performSynthesis(sessionid: sessionid, text: cleanedText)
}
return true
@ -139,7 +148,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
/**
* 流式合成文本
*/
public func speakStream(_ text: String) -> Bool {
public func speakStream(sessionid sessionid: String,_ text: String) -> Bool {
if !isInitialized {
notifyEvent(eventType: .error, params: [
"errorCode": "NOT_INITIALIZED",
@ -147,6 +156,10 @@ public class AzureTtsHelper: NSObject, ITtsService {
])
return false
}
if (self.sessionid != sessionid){
return false
}
self.sessionid = sessionid
if text.isEmpty {
return true
@ -184,7 +197,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
streamBuffer = String(currentText[startIndex...])
if !textToSpeak.isEmpty {
return speakOnce(textToSpeak)
return speakOnce(sessionid: sessionid, textToSpeak)
}
}
@ -194,7 +207,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
/**
* 刷新并播放流式文本
*/
public func flushStream() -> Bool {
public func flushStream(sessionid sessionid: String) -> Bool {
if !isInitialized {
notifyEvent(eventType: .error, params: [
"errorCode": "NOT_INITIALIZED",
@ -210,7 +223,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
return true
}
return speakOnce(remainingText)
return speakOnce(sessionid:sessionid,remainingText)
}
/**
@ -218,14 +231,15 @@ public class AzureTtsHelper: NSObject, ITtsService {
*/
public func stop() -> Bool {
speaking = false
sessionid = ""
streamBuffer = ""
lastSpokenText = ""
taskLock.lock()
pendingTasks.removeAll()
taskLock.unlock()
synthesisQueue.async {
do {
print("liwei-------------停止语音合成播放")
try self.synthesizer?.stopSpeaking()
self.notifyEvent(eventType: .synthesisCanceled)
} catch {
@ -235,6 +249,14 @@ public class AzureTtsHelper: NSObject, ITtsService {
"errorMessage": error.localizedDescription
])
}
synthesisQueue.async {
do {
self.synthesizer = nil
try self.recreateSynthesizer()
}catch {
os_log("重置语音合成失败: %{public}@", log: self.log, type: .error, error.localizedDescription)
}
}
return true
@ -314,7 +336,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
// 如果之前正在播放,恢复播放
if wasSpeaking, let lastText = lastSpokenText {
speakOnce(lastText)
speakOnce(sessionid: "",lastText)
}
return true
@ -386,13 +408,19 @@ public class AzureTtsHelper: NSObject, ITtsService {
/**
* 执行语音合成
*/
private func performSynthesis(text: String) {
private func performSynthesis(sessionid:String,text: String) {
if (self.sessionid != sessionid) {
return
}
speaking = true
lastSpokenText = text
// lastSpokenText = text
// 生成SSML
let ssml = generateOptimizedSsml(text)
do {
if (self.sessionid != sessionid) {
return
}
os_log("开始合成: %{public}@", log: log, type: .debug, ssml)
let result = try synthesizer?.startSpeakingSsml(ssml)

10
local_plugins/speech/ios/speech/Sources/speech/ITtsService.swift

@ -92,7 +92,11 @@ import Foundation
* @param text 要合成的文本
* @return 是否成功开始合成
*/
func speakOnce(_ text: String) -> Bool
func speakOnce(sessionid sessionid: String,_ text: String) -> Bool
//开始播报
func startspeak(sessionid sessionid: String)-> Bool
/**
* 流式合成
@ -101,14 +105,14 @@ import Foundation
* @param text 要合成的文本片段
* @return 是否成功处理
*/
func speakStream(_ text: String) -> Bool
func speakStream(sessionid sessionid: String,_ text: String) -> Bool
/**
* 刷新并播放流式文本缓冲区中的剩余内容
*
* @return 是否成功处理
*/
func flushStream() -> Bool
func flushStream(sessionid sessionid: String) -> Bool
/**
* 停止当前合成和播放

Loading…
Cancel
Save