Java后端代码
maven 仓库:
<!--openAI 请求工具-->
<dependency>
<groupId>com.unfbx</groupId>
<artifactId>chatgpt-java</artifactId>
<version>1.1.5</version>
</dependency>
maven 仓库官方 tts 使用案例:
@SneakyThrows
@Test
public void textToSpeed() {
TextToSpeech textToSpeech = TextToSpeech.builder()
.model(TextToSpeech.Model.TTS_1.getName())
.input("OpenAI官方Api的Java SDK,可以快速接入项目使用。目前支持OpenAI官方全部接口,同时支持Tokens计算。官方github地址:https://github.com/Grt1228/chatgpt-java。欢迎star。")
.voice(TtsVoice.NOVA.getName())
.responseFormat(TtsFormat.MP3.getName())
.build();
java.io.File file = new java.io.File("G:\\test.mp3");
ResponseBody responseBody = client.textToSpeech(textToSpeech);
InputStream inputStream = responseBody.byteStream();
//创建文件
if (!file.exists()) {
if (!file.getParentFile().exists())
file.getParentFile().mkdir();
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
log.error("createNewFile IOException");
}
}
OutputStream os = null;
try {
os = new BufferedOutputStream(new FileOutputStream(file));
byte data[] = new byte[8192];
int len;
while ((len = inputStream.read(data, 0, 8192)) != -1) {
os.write(data, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
控制器 controller
@ApiOperation("文本转语音")
@GetMapping("/textToVoice")
public ResponseEntity<InputStreamResource> textToVoice(String text, HttpServletResponse response) {
response.setContentType("application/octet-stream");
return translationHistoryService.textToVoice(text);
}
业务 service
@Override
public ResponseEntity<InputStreamResource> textToVoice(String text) {
String audioDownloadUrl = ObjectUtils.filterObjectNull(this.lambdaQuery()
.select(TranslationHistory::getAudioDownloadUrl)
.eq(TranslationHistory::getOriginalText, text)
.last(SqlConstants.LIMIT_1).one(), TranslationHistory.class)
.getAudioDownloadUrl();
InputStream inputStream = chatGptService.textToSpeed(text,audioDownloadUrl);
InputStreamResource resource = new InputStreamResource(inputStream);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}
public InputStream textToSpeed(String text,String audioDownloadUrl) {
// 1、查询缓存
String redisKey = RedisKeyUtils.getTextToSpeed(text);
byte[] audioData = RedisUtils.getByte(redisKey);
if (audioData != null) {
log.info("从缓存中返回音频流数据");
return new ByteArrayInputStream(audioData);
}
// 2、调用 api 响应
// 将生成的音频数据读取为字节数组
InputStream inputStream = null;
try {
log.info("从 API 中返回音频流数据");
inputStream = textToSpeedIs(text);
audioData = inputStreamToByteArray(inputStream);
// 将音频缓存到 Redis 中
RedisUtils.setValue(redisKey, audioData,7L, TimeUnit.DAYS);
byte[] finalAudioData = audioData;
// 开辟线程存入 redis
poolSend.send(()->{
// 标记首次
String cacheKey = RedisKeyUtils.getFirstTextToSpeed(text);
RedisUtils.setValue(cacheKey, finalAudioData,2L, TimeUnit.DAYS);
});
} catch (IOException e) {
log.error("openAI音频调用异常:",e );
throw new RuntimeException(e);
} finally {
// 平常的关闭流代码太难看了,写了工具类简洁多了,自己封装一个
CloseableUtils.close(inputStream);
}
// 将文本和对应的音频数据缓存到 Redis 中
RedisUtils.setValue(redisKey, audioData, 7L, TimeUnit.DAYS);
inputStream = new ByteArrayInputStream(audioData);
return inputStream;
}
private InputStream textToSpeedIs(String text) throws IOException {
TextToSpeech textToSpeech = TextToSpeech.builder()
.model(TextToSpeech.Model.TTS_1.getName())
.input(text)
.voice(TtsVoice.NOVA.getName())
.responseFormat(TtsFormat.MP3.getName())
.build();
// (重点,这里的方法细节就不展示了,看官方案例就知道,在哪个基础上复制粘贴封装一下方法即可)
ResponseBody responseBody = chatGptStreamRequest.textToSpeech(textToSpeech,openaiKeyService.getApiKeyStrList());
InputStream inputStream = responseBody.byteStream();
return inputStream;
}
将 inputStream 流转换成 byte[] 数组
public static byte[] inputStreamToByteArray(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
byte[] bytes = byteBuffer.toByteArray();
byteBuffer.close();
inputStream.close(); // 关闭流以释放资源
return bytes;
}
后端代码就是这样,哦对了,还有 redisTemplate 的配置也分享一下,因为要将音频 byte[] 存入缓存,所以单独给 byte[] 类型配置 redisTemplate 注入:
@Bean
public RedisTemplate<String, byte[]> byteRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, byte[]> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
// 使用 ByteArrayRedisSerializer 来处理字节数据
template.setValueSerializer(RedisSerializer.byteArray());
template.afterPropertiesSet();
return template;
}
public static void setValue(String key, byte[] value, Long expireTime, TimeUnit timeUnit) {
redisByteTemplate.opsForValue().set(key, value, expireTime, timeUnit);
}
public static byte[] getByte(String key) {
return redisByteTemplate.opsForValue().get(key);
}
前端代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>音频流测试</title>
</head>
<body>
<h2>音频流播放测试</h2>
<!-- 音频播放组件,初始时不显示 -->
<audio id="audioPlayer" controls style="display: none;"></audio>
<button id="playAudio">播放音频</button>
<script>
document.getElementById('playAudio').addEventListener('click', function() {
// 音频流接口的 URL
var audioUrl = 'http://localhost:8822/client/translation/textToVoice?text=今天很开心1';
// 使用 Fetch API 请求音频流
fetch(audioUrl, {
method: 'GET',
headers: {
// 添加请求头
'token': 'v0Dbf55iGiH8uSfxwrlkvlt12qb57cnj'
}
}).then(function(response) {
// 检查响应是否成功
if (response.ok) {
return response.blob();
}
throw new Error('网络响应错误');
}).then(function(blob) {
// 将 Blob 转换为 URL 并设置给 <audio> 元素
var url = URL.createObjectURL(blob);
var audioPlayer = document.getElementById('audioPlayer');
audioPlayer.src = url;
audioPlayer.style.display = 'block';
audioPlayer.play();
}).catch(function(error) {
console.error('请求音频流失败:', error);
});
});
</script>
</body>
</html>
过程中出现的后端异常
User
No converter for [class cn.hutool.core.io.resource.InputStreamResource] with preset Content-Type 'application/octet-stream'
将 class cn.hutool.core.io.resource.InputStreamResource 切换成 org.springframework.core.io.InputStreamResource 即可
启动调试
嗯,完美运行,下班收工