1"use strict";(self.webpackChunkspring_ai_alibaba_website=self.webpackChunkspring_ai_alibaba_website||[]).push([[1303],{28453:(e,n,t)=>{t.d(n,{R:()=>l,x:()=>s});var o=t(96540);const i={},r=o.createContext(i);function l(e){const n=o.useContext(r);return o.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function s(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),o.createElement(r.Provider,{value:n},e.children)}},98032:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>d,frontMatter:()=>l,metadata:()=>o,toc:()=>a});const o=JSON.parse('{"id":"multimodals/audio/speech","title":"Text-To-Speech (TTS) API","description":"Spring AI \u901a\u8fc7 TextToSpeechModel \u548c StreamingTextToSpeechModel \u63a5\u53e3\u4e3a Text-To-Speech (TTS) \u63d0\u4f9b\u7edf\u4e00\u7684 API\u3002\u8fd9\u5141\u8bb8\u60a8\u7f16\u5199\u53ef\u5728\u4e0d\u540c TTS \u63d0\u4f9b\u8005\u4e4b\u95f4\u5de5\u4f5c\u7684\u53ef\u79fb\u690d\u4ee3\u7801\u3002","source":"@site/integration/multimodals/audio/speech.md","sourceDirName":"multimodals/audio","slug":"/multimodals/audio/speech","permalink":"/integration/multimodals/audio/speech","draft":false,"unlisted":false,"editUrl":"https://github.com/spring-ai-alibaba/website/tree/main/integration/multimodals/audio/speech.md","tags":[],"version":"current","lastUpdatedBy":"txuw","lastUpdatedAt":1768049689000,"frontMatter":{},"sidebar":"integrationSidebar","previous":{"title":"Multimodality API","permalink":"/integration/multimodals/multimodality"},"next":{"title":"DashScope Text-to-Speech (TTS)","permalink":"/integration/multimodals/audio/speech/dashscope-speech"}}');var i=t(74848),r=t(28453);const l={},s="Text-To-Speech (TTS) API",p={},a=[{value:"Supported Providers",id:"supported-providers",level:2},{value:"Common Interface",id:"common-interface",level:2},{value:"TextToSpeechModel",id:"texttospeechmodel",level:3},{value:"StreamingTextToSpeechModel",id:"streamingtexttospeechmodel",level:3},{value:"TextToSpeechPrompt",id:"texttospeechprompt",level:3},{value:"TextToSpeechResponse",id:"texttospeechresponse",level:3},{value:"Writing Provider-Agnostic Code",id:"writing-provider-agnostic-code",level:2},{value:"Basic Service Example",id:"basic-service-example",level:3},{value:"Advanced Example: Multi-Provider Support",id:"advanced-example-multi-provider-support",level:3},{value:"Streaming Audio Example",id:"streaming-audio-example",level:3},{value:"REST Controller Example",id:"rest-controller-example",level:3},{value:"Configuration-Based Provider Selection",id:"configuration-based-provider-selection",level:3},{value:"Using Portable Options",id:"using-portable-options",level:3},{value:"Working with Provider-Specific Features",id:"working-with-provider-specific-features",level:3},{value:"Best Practices for Portable Code",id:"best-practices-for-portable-code",level:3},{value:"Provider-Specific Features",id:"provider-specific-features",level:2}];function c(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"text-to-speech-tts-api",children:"Text-To-Speech (TTS) API"})}),"\n",(0,i.jsxs)(n.p,{children:["Spring AI \u901a\u8fc7 ",(0,i.jsx)(n.code,{children:"TextToSpeechModel"})," \u548c ",(0,i.jsx)(n.code,{children:"StreamingTextToSpeechModel"})," \u63a5\u53e3\u4e3a Text-To-Speech (TTS) \u63d0\u4f9b\u7edf\u4e00\u7684 API\u3002\u8fd9\u5141\u8bb8\u60a8\u7f16\u5199\u53ef\u5728\u4e0d\u540c TTS \u63d0\u4f9b\u8005\u4e4b\u95f4\u5de5\u4f5c\u7684\u53ef\u79fb\u690d\u4ee3\u7801\u3002"]}),"\n",(0,i.jsx)(n.h2,{id:"supported-providers",children:"Supported Providers"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"/integration/multimodals/audio/speech/openai-speech",children:"OpenAI's Speech API"})}),"\n"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:(0,i.jsx)(n.a,{href:"/integration/multimodals/audio/speech/dashscope-speech",children:"DashScope Text-To-Speech API"})}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"common-interface",children:"Common Interface"}),"\n",(0,i.jsx)(n.p,{children:"\u6240\u6709 TTS \u63d0\u4f9b\u8005\u5b9e\u73b0\u4ee5\u4e0b\u5171\u4eab\u63a5\u53e3\uff1a"}),"\n",(0,i.jsx)(n.h3,{id:"texttospeechmodel",children:"TextToSpeechModel"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"TextToSpeechModel"})," \u63a5\u53e3\u63d0\u4f9b\u5c06\u6587\u672c\u8f6c\u6362\u4e3a\u8bed\u97f3\u7684\u65b9\u6cd5\uff1a"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:"public interface TextToSpeechModel extends Model<TextToSpeechPrompt, TextToSpeechResponse>, StreamingTextToSpeechModel {\n\n /**\n * Converts text to speech with default options.\n */\n default byte[] call(String text) {\n // Default implementation\n }\n\n /**\n * Converts text to speech with custom options.\n */\n TextToSpeechResponse call(TextToSpeechPrompt prompt);\n\n /**\n * Returns the default options for this model.\n */\n default TextToSpeechOptions getDefaultOptions() {\n // Default implementation\n }\n}\n"})}),"\n",(0,i.jsx)(n.h3,{id:"streamingtexttospeechmodel",children:"StreamingTextToSpeechModel"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"StreamingTextToSpeechModel"})," \u63a5\u53e3\u63d0\u4f9b\u5b9e\u65f6\u6d41\u5f0f\u4f20\u8f93\u97f3\u9891\u7684\u65b9\u6cd5\uff1a"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:"@FunctionalInterface\npublic interface StreamingTextToSpeechModel extends StreamingModel<TextToSpeechPrompt, TextToSpeechResponse> {\n\n /**\n * Streams text-to-speech responses with metadata.\n */\n Flux<TextToSpeechResponse> stream(TextToSpeechPrompt prompt);\n\n /**\n * Streams audio bytes for the given text.\n */\n default Flux<byte[]> stream(String text) {\n // Default implementation\n }\n}\n"})}),"\n",(0,i.jsx)(n.h3,{id:"texttospeechprompt",children:"TextToSpeechPrompt"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"TextToSpeechPrompt"})," \u7c7b\u5c01\u88c5\u8f93\u5165\u6587\u672c\u548c\u9009\u9879\uff1a"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:'TextToSpeechPrompt prompt = new TextToSpeechPrompt(\n "Hello, this is a text-to-speech example.",\n options\n);\n'})}),"\n",(0,i.jsx)(n.h3,{id:"texttospeechresponse",children:"TextToSpeechResponse"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"TextToSpeechResponse"})," \u7c7b\u5305\u542b\u751f\u6210\u7684\u97f3\u9891\u548c\u5143\u6570\u636e\uff1a"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:"TextToSpeechResponse response = model.call(prompt);\nbyte[] audioBytes = response.getResult().getOutput();\nTextToSpeechResponseMetadata metadata = response.getMetadata();\n"})}),"\n",(0,i.jsx)(n.h2,{id:"writing-provider-agnostic-code",children:"Writing Provider-Agnostic Code"}),"\n",(0,i.jsx)(n.p,{children:"\u5171\u4eab TTS \u63a5\u53e3\u7684\u4e3b\u8981\u597d\u5904\u4e4b\u4e00\u662f\u80fd\u591f\u7f16\u5199\u53ef\u5728\u4efb\u4f55 TTS \u63d0\u4f9b\u8005\u4e0a\u5de5\u4f5c\u800c\u65e0\u9700\u4fee\u6539\u7684\u4ee3\u7801\u3002\u5b9e\u9645\u7684\u63d0\u4f9b\u8005\uff08OpenAI\u3001ElevenLabs \u7b49\uff09\u7531\u60a8\u7684 Spring Boot \u914d\u7f6e\u786e\u5b9a\uff0c\u5141\u8bb8\u60a8\u5728\u4e0d\u66f4\u6539\u5e94\u7528\u7a0b\u5e8f\u4ee3\u7801\u7684\u60c5\u51b5\u4e0b\u5207\u6362\u63d0\u4f9b\u8005\u3002"}),"\n",(0,i.jsx)(n.h3,{id:"basic-service-example",children:"Basic Service Example"}),"\n",(0,i.jsx)(n.p,{children:"\u5171\u4eab\u63a5\u53e3\u5141\u8bb8\u60a8\u7f16\u5199\u53ef\u5728\u4efb\u4f55 TTS \u63d0\u4f9b\u8005\u4e0a\u5de5\u4f5c\u7684\u4ee3\u7801\uff1a"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:"@Service\npublic class NarrationService {\n\n private final TextToSpeechModel textToSpeechModel;
1\n\n public NarrationService(TextToSpeechModel textToSpeechModel) {\n this.textToSpeechModel = textToSpeechModel;\n }\n\n public byte[] narrate(String text) {\n // Works with any TTS provider\n return textToSpeechModel.call(text);\n }\n\n public byte[] narrateWithOptions(String text, TextToSpeechOptions options) {\n TextToSpeechPrompt prompt = new TextToSpeechPrompt(text, options);\n TextToSpeechResponse response = textToSpeechModel.call(prompt);\n return response.getResult().getOutput();\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"\u6b64\u670d\u52a1\u53ef\u4e0e OpenAI\u3001ElevenLabs \u6216\u4efb\u4f55\u5176\u4ed6 TTS \u63d0\u4f9b\u8005\u65e0\u7f1d\u534f\u4f5c\uff0c\u5b9e\u9645\u5b9e\u73b0\u7531\u60a8\u7684 Spring Boot \u914d\u7f6e\u786e\u5b9a\u3002"}),"\n",(0,i.jsx)(n.h3,{id:"advanced-example-multi-provider-support",children:"Advanced Example: Multi-Provider Support"}),"\n",(0,i.jsx)(n.p,{children:"\u60a8\u53ef\u4ee5\u6784\u5efa\u540c\u65f6\u652f\u6301\u591a\u4e2a TTS \u63d0\u4f9b\u8005\u7684\u5e94\u7528\u7a0b\u5e8f\uff1a"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:'@Service\npublic class MultiProviderNarrationService {\n\n private final Map<String, TextToSpeechModel> providers;\n\n public MultiProviderNarrationService(List<TextToSpeechModel> models) {\n // Spring will inject all available TextToSpeechModel beans\n this.providers = models.stream()\n .collect(Collectors.toMap(\n model -> model.getClass().getSimpleName(),\n model -> model\n ));\n }\n\n public byte[] narrateWithProvider(String text, String providerName) {\n TextToSpeechModel model = providers.get(providerName);\n if (model == null) {\n throw new IllegalArgumentException("Unknown provider: " + providerName);\n }\n return model.call(text);\n }\n\n public Set<String> getAvailableProviders() {\n return providers.keySet();\n }\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"streaming-audio-example",children:"Streaming Audio Example"}),"\n",(0,i.jsx)(n.p,{children:"\u5171\u4eab\u63a5\u53e3\u8fd8\u652f\u6301\u5b9e\u65f6\u97f3\u9891\u751f\u6210\u7684\u6d41\u5f0f\u4f20\u8f93\uff1a"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:"@Service\npublic class StreamingNarrationService {\n\n private final TextToSpeechModel textToSpeechModel;\n\n public StreamingNarrationService(TextToSpeechModel textToSpeechModel) {\n this.textToSpeechModel = textToSpeechModel;\n }\n\n public Flux<byte[]> streamNarration(String text) {\n // TextToSpeechModel extends StreamingTextToSpeechModel\n return textToSpeechModel.stream(text);\n }\n\n public Flux<TextToSpeechResponse> streamWithMetadata(String text, TextToSpeechOptions options) {\n TextToSpeechPrompt prompt = new TextToSpeechPrompt(text, options);\n return textToSpeechModel.stream(prompt);\n }\n}\n"})}),"\n",(0,i.jsx)(n.h3,{id:"rest-controller-example",children:"REST Controller Example"}),"\n",(0,i.jsx)(n.p,{children:"\u4f7f\u7528\u4e0e\u63d0\u4f9b\u8005\u65e0\u5173\u7684 TTS \u6784\u5efa REST API\uff1a"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:'@RestController\n@RequestMapping("/api/tts")\npublic class TextToSpeechController {\n\n private final TextToSpeechModel textToSpeechModel;\n\n public TextToSpeechController(TextToSpeechModel textToSpeechModel) {\n this.textToSpeechModel = textToSpeechModel;\n }\n\n @PostMapping(value = "/synthesize", produces = "audio/mpeg")\n public ResponseEntity<byte[]> synthesize(@RequestBody SynthesisRequest request) {\n byte[] audio = textToSpeechModel.call(request.text());\n return ResponseEntity.ok()\n .contentType(MediaType.parseMediaType("audio/mpeg"))\n .header("Content-Disposition", "attachment; filename=\\"speech.mp3\\"")\n .body(audio);\n }\n\n @GetMapping(value = "/stream", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)\n public Flux<byte[]> streamSynthesis(@RequestParam String text) {\n return textToSpeechModel.stream(text);\n }\n\n record SynthesisRequest(String text) {}\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"configuration-based-provider-selection",children:"Configuration-Based Provider Selection"}),"\n",(0,i.jsx)(n.p,{children:"\u4f7f\u7528 Spring profiles \u6216\u5c5e\u6027\u5728\u63d0\u4f9b\u8005\u4e4b\u95f4\u5207\u6362\uff1a"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-yaml",children:"# application-openai.yml\nspring:\n ai:\n model:\n audio:\n speech: openai\n openai:\n api-key: ${OPENAI_API_KEY}\n audio:\n speech:\n options:\n model: gpt-4o-mini-tts\n voice: alloy\n\n# application-elevenlabs.yml\nspring:\n ai:\n model:\n audio:\n speech: elevenlabs\n elevenlabs:\n api-key: ${ELEVENLABS_API_KEY}\n tts:\n options:\n model-id: eleven_turbo_v2_5\n voice-id: your_voice_id\n"})}),"\n",(0,i.jsx)(n.p,{children:"\u7136\u540e\u6fc0\u6d3b\u6240\u9700\u7684\u63d0\u4f9b\u8005\uff1a"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Use OpenAI\njava -jar app.jar --spring.profiles.active=openai\n\n# Use ElevenLabs\njava -jar app.jar --spring.profiles.active=elevenlabs\n"})}),"\n",(0,i.jsx)(n.h3,{id:"using-portable-options",children:"Using Portable Options"}),"\n",(0,i.jsxs)(n.p,{children:["\u4e3a\u4e86\u6700\u5927\u53ef\u79fb\u
1690d\u6027\uff0c\u4ec5\u4f7f\u7528\u901a\u7528 ",(0,i.jsx)(n.code,{children:"TextToSpeechOptions"})," \u63a5\u53e3\u65b9\u6cd5\uff1a"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:"@Service\npublic class PortableNarrationService {\n\n private final TextToSpeechModel textToSpeechModel;\n\n public PortableNarrationService(TextToSpeechModel textToSpeechModel) {\n this.textToSpeechModel = textToSpeechModel;\n }\n\n public byte[] createPortableNarration(String text) {\n // Use provider's default options for maximum portability\n TextToSpeechOptions defaultOptions = textToSpeechModel.getDefaultOptions();\n TextToSpeechPrompt prompt = new TextToSpeechPrompt(text, defaultOptions);\n TextToSpeechResponse response = textToSpeechModel.call(prompt);\n return response.getResult().getOutput();\n }\n}\n"})}),"\n",(0,i.jsx)(n.h3,{id:"working-with-provider-specific-features",children:"Working with Provider-Specific Features"}),"\n",(0,i.jsx)(n.p,{children:"\u5f53\u60a8\u9700\u8981\u63d0\u4f9b\u8005\u7279\u5b9a\u529f\u80fd\u65f6\uff0c\u60a8\u4ecd\u7136\u53ef\u4ee5\u5728\u4fdd\u6301\u53ef\u79fb\u690d\u4ee3\u7801\u5e93\u7684\u540c\u65f6\u4f7f\u7528\u5b83\u4eec\uff1a"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-java",children:'@Service\npublic class FlexibleNarrationService {\n\n private final TextToSpeechModel textToSpeechModel;\n\n public FlexibleNarrationService(TextToSpeechModel textToSpeechModel) {\n this.textToSpeechModel = textToSpeechModel;\n }\n\n public byte[] narrate(String text, TextToSpeechOptions baseOptions) {\n TextToSpeechOptions options = baseOptions;\n\n // Apply provider-specific optimizations if available\n if (textToSpeechModel instanceof OpenAiAudioSpeechModel) {\n options = OpenAiAudioSpeechOptions.builder()\n .from(baseOptions)\n .model("gpt-4o-tts") // OpenAI-specific: use high-quality model\n .speed(1.0)\n .build();\n } else if (textToSpeechModel instanceof ElevenLabsTextToSpeechModel) {\n // ElevenLabs-specific options could go here\n }\n\n TextToSpeechPrompt prompt = new TextToSpeechPrompt(text, options);\n TextToSpeechResponse response = textToSpeechModel.call(prompt);\n return response.getResult().getOutput();\n }\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"best-practices-for-portable-code",children:"Best Practices for Portable Code"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\u4f9d\u8d56\u63a5\u53e3"}),"\uff1a\u59cb\u7ec8\u6ce8\u5165 ",(0,i.jsx)(n.code,{children:"TextToSpeechModel"})," \u800c\u4e0d\u662f\u5177\u4f53\u5b9e\u73b0"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\u4f7f\u7528\u901a\u7528\u9009\u9879"}),"\uff1a\u575a\u6301\u4f7f\u7528 ",(0,i.jsx)(n.code,{children:"TextToSpeechOptions"})," \u63a5\u53e3\u65b9\u6cd5\u4ee5\u83b7\u5f97\u6700\u5927\u53ef\u79fb\u690d\u6027"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\u4f18\u96c5\u5904\u7406\u5143\u6570\u636e"}),"\uff1a\u4e0d\u540c\u7684\u63d0\u4f9b\u8005\u8fd4\u56de\u4e0d\u540c\u7684\u5143\u6570\u636e\uff1b\u901a\u7528\u5730\u5904\u7406\u5b83"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\u4f7f\u7528\u591a\u4e2a\u63d0\u4f9b\u8005\u8fdb\u884c\u6d4b\u8bd5"}),"\uff1a\u786e\u4fdd\u60a8\u7684\u4ee3\u7801\u81f3\u5c11\u4e0e\u4e24\u4e2a TTS \u63d0\u4f9b\u8005\u4e00\u8d77\u5de5\u4f5c"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"\u8bb0\u5f55\u63d0\u4f9b\u8005\u5047\u8bbe"}),"\uff1a\u5982\u679c\u60a8\u4f9d\u8d56\u7279\u5b9a\u7684\u63d0\u4f9b\u8005\u884c\u4e3a\uff0c\u8bf7\u6e05\u695a\u5730\u8bb0\u5f55\u5b83"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"provider-specific-features",children:"Provider-Specific Features"}),"\n",(0,i.jsxs)(n.p,{children:["\u867d\u7136\u5171\u4eab\u63a5\u53e3\u63d0\u4f9b\u4e86\u53ef\u79fb\u690d\u6027\uff0c\u4f46\u6bcf\u4e2a\u63d0\u4f9b\u8005\u8fd8\u901a\u8fc7\u63d0\u4f9b\u8005\u7279\u5b9a\u7684\u9009\u9879\u7c7b\uff08\u4f8b\u5982 ",(0,i.jsx)(n.code,{children:"OpenAiAudioSpeechOptions"}),"\u3001",(0,i.jsx)(n.code,{children:"ElevenLabsSpeechOptions"}),"\uff09\u63d0\u4f9b\u7279\u5b9a\u529f\u80fd\u3002\u8fd9\u4e9b\u7c7b\u5b9e\u73b0 ",(0,i.jsx)(n.code,{children:"TextToSpeechOptions"})," \u63a5\u53e3\uff0c\u540c\u65f6\u6dfb\u52a0\u63d0\u4f9b\u8005\u7279\u5b9a\u7684\u529f\u80fd\u3002"]})]})}function d(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.