Spring AI 中的 OpenAI 文字轉語音 (TTS) 指南
1. 簡介
如今,應用程式從神經網路整合中受益匪淺,例如知識庫、助手或分析引擎。一個實際的用例是將文字轉換為語音。這個過程稱為文字轉語音 (TTS),可以自動創建具有自然、類似人類聲音的音訊內容。
現代 TTS 系統使用深度學習來處理發音、節奏、語調甚至情緒。與早期基於規則的方法不同,這些模型在大型資料集上進行訓練,可以產生富有表現力的多語言語音,這對於虛擬助理或包容性教育平台等全球應用來說是理想的。
在本教學中,我們將探討如何將 OpenAI 文字轉語音與 Spring AI 結合使用。
2.依賴項和配置
我們首先加入spring-ai-starter-model-openai
依賴項:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>1.1.0</version>
</dependency>
接下來,我們將為 OpenAI 模型配置 Spring AI 屬性:
spring.ai.openai.api-key=${OPENAI_API_KEY} spring.ai.openai.audio.speech.options.model=tts-1 spring.ai.openai.audio.speech.options.voice=alloy spring.ai.openai.audio.speech.options.response-format=mp3 spring.ai.openai.audio.speech.options.speed=1.0
要使用 OpenAI API,我們必須設定Open AI API 金鑰。我們還必須指定文字到語音模型名稱、語音、回應格式和音訊速度。
3. 建立文字轉語音應用程式
現在,我們將建立我們的文字轉語音應用程式。 首先,我們將建立TextToSpeechService:
@Service
public class TextToSpeechService {
private OpenAiAudioSpeechModel openAiAudioSpeechModel;
@Autowired
public TextToSpeechService(OpenAiAudioSpeechModel openAiAudioSpeechModel) {
this.openAiAudioSpeechModel = openAiAudioSpeechModel;
}
public byte[] makeSpeech(String text) {
SpeechPrompt speechPrompt = new SpeechPrompt(text);
SpeechResponse response = openAiAudioSpeechModel.call(speechPrompt);
return response.getResult().getOutput();
}
}
在這裡,我們使用OpenAiAudioSpeechModel
,Spring AI 使用我們的屬性對其進行了預先配置。我們也定義了makeSpeech()
方法,使用OpenAiAudioSpeechModel
將文字轉換為音訊檔案位元組。
接下來,我們創建TextToSpeechController
:
@RestController
public class TextToSpeechController {
private final TextToSpeechService textToSpeechService;
@Autowired
public TextToSpeechController(TextToSpeechService textToSpeechService) {
this.textToSpeechService = textToSpeechService;
}
@GetMapping("/text-to-speech")
public ResponseEntity<byte[]> generateSpeechForText(@RequestParam String text) {
return ResponseEntity.ok(textToSpeechService.makeSpeech(text));
}
}
最後,我們測試我們的端點:
@SpringBootTest
@ExtendWith(SpringExtension.class)
@AutoConfigureMockMvc
@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*")
class TextToSpeechLiveTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private TextToSpeechService textToSpeechService;
@Test
void givenTextToSpeechService_whenCallingTextToSpeechEndpoint_thenExpectedAudioFileBytesShouldBeObtained() throws Exception {
byte[] audioContent = mockMvc.perform(get("/text-to-speech")
.param("text", "Hello from Baeldung"))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsByteArray();
assertNotEquals(0, audioContent.length);
}
}
我們調用文字轉語音端點並驗證回應代碼和非空內容。如果我們將內容儲存到檔案中,我們將得到一個包含我們演講的 MP3 檔案。
4. 新增串流即時音訊端點
當我們在一個巨大的位元組數組中獲取大量音訊內容時,我們可能會面臨大量的記憶體消耗。另外,有時我們想在音訊完全上傳之前播放它。為此,OpenAI 支援串流文字轉語音回應。
讓我們擴展TextToSpeechService
來支援此功能:
public Flux<byte[]> makeSpeechStream(String text) {
SpeechPrompt speechPrompt = new SpeechPrompt(text);
Flux<SpeechResponse> responseStream = openAiAudioSpeechModel.stream(speechPrompt);
return responseStream
.map(SpeechResponse::getResult)
.map(Speech::getOutput);
}
我們加入了makeSpeechStream()
方法。在這裡,我們使用OpenAiAudioSpeechModel
的stream()
方法來產生位元組區塊流。
接下來,我們建立透過 HTTP 傳輸位元組的端點:
@GetMapping(value = "/text-to-speech-stream", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<StreamingResponseBody> streamSpeech(@RequestParam("text") String text) {
Flux<byte[]> audioStream = textToSpeechService.makeSpeechStream(text);
StreamingResponseBody responseBody = outputStream -> {
audioStream.toStream().forEach(bytes -> {
try {
outputStream.write(bytes);
outputStream.flush();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
};
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(responseBody);
}
在這裡我們迭代位元組流並將其寫入StreamingResponseBody
。如果我們使用 WebFlux,我們將直接從端點傳回Flux
。作為一種選擇,我們可以使用 application/octet-stream 內容類型來指示回應是一個流。
現在讓我們測試一下串流方法:
@Test
void givenStreamingEndpoint_whenCalled_thenReceiveAudioFileBytes() throws Exception {
String longText = """
Hello from Baeldung!
Here, we explore the world of Java,
Spring, and web development with clear, practical tutorials.
Whether you're just starting out or diving deep into advanced
topics, you'll find guides to help you write clean, efficient,
and modern code.
""";
mockMvc.perform(get("/text-to-speech-stream")
.param("text", longText)
.accept(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(status().isOk())
.andDo(result -> {
byte[] response = result.getResponse().getContentAsByteArray();
assertNotNull(response);
assertTrue( response.length > 0);
});
}
在這裡我們呼叫我們的流端點並驗證它傳回一個位元組數組。 MockMvc 收集完整的回應主體,但我們也可以將其作為流讀取。
5. 自訂特定呼叫的模型參數
有時我們需要覆蓋特定呼叫的模型選項。為此,我們可以使用OpenAiAudioSpeechOptions
。讓我們更新TextToSpeechService
以支援自訂語音選項:
public byte[] makeSpeech(String text, OpenAiAudioSpeechOptions speechOptions) {
SpeechPrompt speechPrompt = new SpeechPrompt(text, speechOptions);
SpeechResponse response = openAiAudioSpeechModel.call(speechPrompt);
return response.getResult().getOutput();
}
我們覆蓋了makeSpeech()
並加入了OpenAiAudioSpeechOptions
參數。我們將其用作呼叫 OpenAI API 的參數。如果我們傳遞一個空對象,則將套用預設選項。
現在我們建立另一個接受語音參數的端點:
@GetMapping("/text-to-speech-customized")
public ResponseEntity<byte[]> generateSpeechForTextCustomized(@RequestParam("text") String text, @RequestParam Map<String, String> params) {
OpenAiAudioSpeechOptions speechOptions = OpenAiAudioSpeechOptions.builder()
.model(params.get("model"))
.voice(OpenAiAudioApi.SpeechRequest.Voice.valueOf(params.get("voice")))
.responseFormat(OpenAiAudioApi.SpeechRequest.AudioResponseFormat.valueOf(params.get("responseFormat")))
.speed(Float.parseFloat(params.get("speed")))
.build();
return ResponseEntity.ok(textToSpeechService.makeSpeech(text, speechOptions));
}
這裡我們得到一個語音參數圖並建構OpenAiAudioSpeechOptions
。
最後,讓我們測試一下新的端點:
@Test
void givenTextToSpeechService_whenCallingTextToSpeechEndpointWithAnotherVoiceOption_thenExpectedAudioFileBytesShouldBeObtained() throws Exception {
byte[] audioContent = mockMvc.perform(get("/text-to-speech-customized")
.param("text", "Hello from Baeldung")
.param("model", "tts-1")
.param("voice", "NOVA")
.param("responseFormat", "MP3")
.param("speed", "1.0"))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsByteArray();
assertNotEquals(0, audioContent.length);
}
我們呼叫了端點並使用NOVA
語音提出此請求。正如預期的那樣,我們收到了帶有覆蓋聲音的音訊位元組。
6. 結論
文字轉語音 API 可以從文字產生自然語音。透過簡單的配置和現代模型,我們可以將動態的口頭互動引入我們的應用程式。
在本文中,我們探討如何使用 Spring AI 將我們的應用程式與 OpenAI TTS 模型整合。同樣,我們可以與其他 TTS 模型整合或建立自己的模型。
與往常一樣,程式碼可在 GitHub 上取得。