feat: 语音交互调整

This commit is contained in:
stream 2025-09-30 15:35:52 +08:00
parent ac37baf54b
commit b900d854a8
3 changed files with 120 additions and 33 deletions

View File

@ -92,22 +92,20 @@ public class FlowNodeParamPreparer {
Object loopNumVal = input.get("loopNum");
int loopCount = 0;
if (loopNumVal instanceof Number) {
loopCount = ((Number) loopNumVal).intValue();
} else if (loopNumVal instanceof JSONArray) {
// 支持 JSON 数组
loopCount = ((JSONArray) loopNumVal).size();
}
else if (loopNumVal instanceof Collection) {
// 支持所有 Java Collection
loopCount = ((Collection<?>) loopNumVal).size();
}
else if (loopNumVal != null && loopNumVal.getClass().isArray()) {
// 支持 Java 数组
loopCount = Array.getLength(loopNumVal);
} else if (loopNumVal != null) {
// 根据迭代路径找到当前层的集合对象
Object target = resolveLoopTarget(loopNumVal, rootMessage.getIterations(), 0);
if (target instanceof JSONArray) {
loopCount = ((JSONArray) target).size();
} else if (target instanceof Collection) {
loopCount = ((Collection<?>) target).size();
} else if (target != null && target.getClass().isArray()) {
loopCount = Array.getLength(target);
} else if (target instanceof Number) {
loopCount = ((Number) target).intValue();
} else if (target != null) {
try {
loopCount = Integer.parseInt(loopNumVal.toString());
loopCount = Integer.parseInt(target.toString());
} catch (NumberFormatException e) {
throw new GlobalException("loopNum 参数格式错误: " + loopNumVal);
}
@ -165,4 +163,42 @@ public class FlowNodeParamPreparer {
}
return curr;
}
/**
* 根据迭代路径iterations逐层解析嵌套集合
* 例子
* - iterations = [] 返回最外层对象
* - iterations = [2] 返回 list[1]
* - iterations = [2,3] 返回 list[1][2]
*/
private Object resolveLoopTarget(Object obj, List<Integer> iterations, int depth) {
if (obj == null) return null;
// 已经走到当前循环层返回目标
if (depth >= iterations.size()) {
return obj;
}
int idx = iterations.get(depth) - 1;
if (obj instanceof List) {
List<?> list = (List<?>) obj;
if (idx >= 0 && idx < list.size()) {
return resolveLoopTarget(list.get(idx), iterations, depth + 1);
}
} else if (obj instanceof JSONArray) {
JSONArray arr = (JSONArray) obj;
if (idx >= 0 && idx < arr.size()) {
return resolveLoopTarget(arr.get(idx), iterations, depth + 1);
}
} else if (obj != null && obj.getClass().isArray()) {
int length = Array.getLength(obj);
if (idx >= 0 && idx < length) {
return resolveLoopTarget(Array.get(obj, idx), iterations, depth + 1);
}
}
// 如果不是集合类型直接返回
return obj;
}
}

View File

@ -131,7 +131,7 @@ public class LLMTTSOperateService implements LLMOperateService {
Map<String, Object> body = new HashMap<>();
body.put("text", text);
body.put("dialect", "普通话");
body.put("timbre", "标准男");
body.put("timbre", "员工女");
body.put("language", "中文");
body.put("tone", "中性");
@ -219,5 +219,4 @@ public class LLMTTSOperateService implements LLMOperateService {
private final long generateMs;
private final CountDownLatch latch; // 仅最后一段有
}
}

View File

@ -7,11 +7,17 @@ import com.cmvr.edge.client.service.EdgeSpeakerService;
import com.cmvr.test.enums.ActionEnum;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteResult;
import com.cmvr.test.flow.runtime.operator.edge.EdgeOperateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import java.io.File;
@Slf4j
@Service
@RequiredArgsConstructor
@ -34,21 +40,67 @@ public class ViPlayOperateService implements VIOperateService {
String deviceId = inputParams.getString("deviceId");
String audioPath = inputParams.getString("audioPath");
String terminalId = message.getTerminalId();
log.info("播放语料成功,deviceId={},audioPath={},terminalId={}", deviceId, audioPath, terminalId);
// edgeSpeakerService.playAudio(terminalId, deviceId, audioPath);
// // 等待播放完成
// while (true) {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// SpeakerCommand.GetSpeakerStateCommand.Feedback status = edgeSpeakerService.getStatus(terminalId, deviceId);
// boolean isRunning = status.getState().getIsRunning();
// if (!isRunning) {
// break;
// }
// }
// log.info("播放语料成功,deviceId={},audioPath={},terminalId={}", deviceId, audioPath, terminalId);
edgeSpeakerService.playAudio(terminalId, deviceId, audioPath);
// 等待播放完成
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
SpeakerCommand.GetSpeakerStateCommand.Feedback status = edgeSpeakerService.getStatus(terminalId, deviceId);
boolean isRunning = status.getState().getIsRunning();
if (!isRunning) {
break;
}
}
// todo 本机测试播放
// playAudio(audioPath);
return TaskNodeExecuteResult.success();
}
/**
* 播放一个音频文件阻塞直到播完
*/
private void playAudio(String filePath) {
try (AudioInputStream sourceStream = AudioSystem.getAudioInputStream(new File(filePath))) {
AudioFormat baseFormat = sourceStream.getFormat();
AudioFormat targetFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16,
baseFormat.getChannels(),
baseFormat.getChannels() * 2,
baseFormat.getSampleRate(),
false
);
try (AudioInputStream pcmStream = AudioSystem.getAudioInputStream(targetFormat, sourceStream)) {
DataLine.Info info = new DataLine.Info(SourceDataLine.class, targetFormat);
try (SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info)) {
line.open(targetFormat);
line.start();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = pcmStream.read(buffer, 0, buffer.length)) != -1) {
line.write(buffer, 0, bytesRead);
}
// 确保缓冲区播完
line.drain();
line.stop();
}
}
log.info("播放完成: {}", filePath);
} catch (Exception e) {
throw new GlobalException("播放失败: ", e);
}
}
}