如何從文字合成語音

參考文件 | 套件 (NuGet) | GitHub 上的其他範例

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

選取合成語言和語音

語音服務中的文字轉換語音功能支援 400 種以上的語音和 140 種以上的語言和變體。 您可以取得完整清單,或在語音資源庫中試用。

指定符合您輸入文字的 SpeechConfig 語言或語音,並使用指定的語音。 下列程式碼片段顯示這項技術的運作方式:

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    // Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
    speechConfig.SpeechSynthesisLanguage = "en-US"; 
    speechConfig.SpeechSynthesisVoiceName = "en-US-AvaMultilingualNeural";
}

所有神經語音都是多語系,而且可流利地使用其自己的語言和英文。 例如,如果英文的輸入文字是「I'm excited to try text to speech」,而且您設定了 es-ES-ElviraNeural,則會使用西班牙文腔調以英文讀出該文字。

如果語音不說輸入文字的語言,語音服務就不會建立合成的音訊。 如需將支援的神精語音列出的完整清單,請參閱語音服務的語言和語音支援

注意

預設語音是從語音清單 API 針對每個地區設定傳回的第一個語音。

說話語音會依下列優先順序來決定:

  • 如果您未設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會說出 en-US 的預設語音。
  • 如果您只設定 SpeechSynthesisLanguage,則說出指定地區設定的預設語音。
  • 如果同時設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會忽略 SpeechSynthesisLanguage 設定, 您使用 SpeechSynthesisVoiceName 語音指定的聲音。
  • 如果語音元素是使用語音合成標記語言 (SSML) 設定,則會忽略 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 設定。

總而言之,優先順序的順序可以描述為:

SpeechSynthesisVoiceName SpeechSynthesisLanguage SSML 結果
en-US 語音的預設聲音
所指定地區設定語音的預設聲音。
您使用 SpeechSynthesisVoiceName 語音指定的聲音。
您使用 SSML 語音指定的聲音。

將語音合成至檔案

建立 SpeechSynthesizer 物件。 下列片段顯示的物件會執行文字轉換語音並輸出至喇叭、檔案或其他輸出資料流。 SpeechSynthesizer 接受下列參數:

  • 您在上一個步驟中建立的 SpeechConfig 物件。
  • AudioConfig 物件,指定應如何處理輸出結果。
  1. 請建立 AudioConfig 執行個體,以使用 FromWavFileOutput() 函式將輸出自動寫入至 .wav 檔案。 使用 using 陳述式將其具現化。

    static async Task SynthesizeAudioAsync()
    {
        var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
        using var audioConfig = AudioConfig.FromWavFileOutput("path/to/write/file.wav");
    }
    

    此內容中的 using 陳述式會自動處置非受控資源,並在處置後導致物件超出範圍。

  2. 使用另一個 using 陳述式來具現化 SpeechSynthesizer 執行個體。 以參數形式傳遞 speechConfig 物件和 audioConfig 物件。 若要合成語音並寫入檔案,請使用文字字串執行 SpeakTextAsync()

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    using var audioConfig = AudioConfig.FromWavFileOutput("path/to/write/file.wav");
    using var speechSynthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
    await speechSynthesizer.SpeakTextAsync("I'm excited to try text to speech");
}

當您執行程式時,這會建立合成的 .wav 檔案,該檔案會寫入您指定的位置。 此結果是最基本用法的絕佳範例。 接下來,您可以自訂輸出,以及如何將輸出回應當作記憶體內部資料流處理,以便處理自訂案例。

合成為喇叭輸出

若要將合成語音輸出到目前使用中輸出裝置 (例如喇叭),請在建立 SpeechSynthesizer 執行個體時省略 AudioConfig 參數。 以下是範例:

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    using var speechSynthesizer = new SpeechSynthesizer(speechConfig);
    await speechSynthesizer.SpeakTextAsync("I'm excited to try text to speech");
}

以記憶體內部資料流的形式取得結果

您可以使用產生的音訊資料作為記憶體內部串流,而非直接寫入至檔案。 使用記憶體內部串流,您可以建置自訂行為:

  • 將產生的位元組陣列摘要成為自訂下游服務的可搜尋資料流。
  • 將結果與其他 API 或服務整合。
  • 修改音訊資料、撰寫自訂 .wav 標頭,以及執行相關工作。

您可以對上一個範例進行這項變更。 首先,移除 AudioConfig 區塊,因為您要從這個時間點開始手動管理輸出行為,以提高掌控權。 在 SpeechSynthesizer 建構函式中針對 AudioConfig 傳遞 null

注意

針對 AudioConfig 傳遞 null (而非如同在上述喇叭輸出範例中加以省略),不會在目前作用中的輸出裝置上依預設播放音訊。

將結果儲存至 SpeechSynthesisResult 變數。 AudioData 屬性包含輸出資料的 byte [] 執行個體。 您可以手動處理此 byte [] 執行個體,也可以使用 AudioDataStream 類別來管理記憶體內部資料流。

在此範例中,您會使用 AudioDataStream.FromResult() 靜態函式從結果中取得資料流:

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    using var speechSynthesizer = new SpeechSynthesizer(speechConfig, null);

    var result = await speechSynthesizer.SpeakTextAsync("I'm excited to try text to speech");
    using var stream = AudioDataStream.FromResult(result);
}

在此階段,您可使用所產生的 stream 物件來實作任何自訂行為。

自訂音訊格式

您可以自訂音訊輸出屬性,包括:

  • 音訊檔類型
  • 採樣速率
  • 位元深度

若要變更音訊格式,請在 SpeechConfig 物件上使用 SetSpeechSynthesisOutputFormat() 函式。 此函式需要 SpeechSynthesisOutputFormat 類型的 enum 執行個體。 使用 enum 來選取輸出格式。 針對可用格式,請參閱音訊格式清單

根據您的需求而定,有各種選項可供不同的檔案類型使用。 依照定義,原始格式 (例如 Raw24Khz16BitMonoPcm) 不包含音訊標頭。 只有在下列其中一種情況下,才使用原始格式:

  • 您知道下游實作可以解碼原始位元資料流。
  • 您計劃根據位元深度、取樣速率和聲道數目等因素來手動建置標頭。

此範例可藉由在 SpeechConfig 物件上設定 SpeechSynthesisOutputFormat,以指定高精確度的 RIFF 格式 Riff24Khz16BitMonoPcm。 與上一節中的範例類似,您可使用 AudioDataStream 來取得結果的記憶體內部資料流,然後將其寫入至檔案。

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    speechConfig.SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm);

    using var speechSynthesizer = new SpeechSynthesizer(speechConfig, null);
    var result = await speechSynthesizer.SpeakTextAsync("I'm excited to try text to speech");

    using var stream = AudioDataStream.FromResult(result);
    await stream.SaveToWaveFileAsync("path/to/write/file.wav");
}

當您執行程式時,這會將 .wav 檔案寫入指定的路徑。

使用 SSML 來自訂語音特性

您可以使用 SSML 從 XML 結構描述提交要求,以微調文字轉換語音輸出中的音調、發音、說話速度、音量和其他方面。 本節示範如何變更語音。 如需詳細資訊,請參閱語音合成標記語言概觀

若要開始使用 SSML 進行自訂,您可進行細微變更來切換語音。

  1. 在根專案目錄中為 SSML 組態建立新的 XML 檔案。

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    在這裡範例中,檔案 ssml.xml。 根元素一律為 <speak>。 將文字包裝在 <voice> 元素中,可讓您使用 name 參數來變更語音。 如需支援的神經語音的完整清單,請參閱支援的語言

  2. 變更語音合成要求以參考您的 XML 檔案。 要求大多數相同,但您會使用 SpeakSsmlAsync(),而不是使用 SpeakTextAsync() 函式。 此函式需要 XML 字串。 首先,使用 File.ReadAllText() 將 SSML 組態載入為字串。 從這點看來,結果物件會與先前的範例完全相同。

    注意

    如果您使用 Visual Studio,在預設情況下,您的組建組態可能找不到您的 XML 檔案。 以滑鼠右鍵按一下 XML 檔案,然後選取 [屬性]。 將 [建置動作] 變更為 [內容]。 將 [複製到輸出目錄] 變更為 [永遠複製]

    public static async Task SynthesizeAudioAsync()
    {
        var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
        using var speechSynthesizer = new SpeechSynthesizer(speechConfig, null);
    
        var ssml = File.ReadAllText("./ssml.xml");
        var result = await speechSynthesizer.SpeakSsmlAsync(ssml);
    
        using var stream = AudioDataStream.FromResult(result);
        await stream.SaveToWaveFileAsync("path/to/write/file.wav");
    }
    

注意

若要在不使用 SSML 的情況下變更語音,您可以使用 SpeechConfig.SpeechSynthesisVoiceName = "en-US-AvaMultilingualNeural";,在 SpeechConfig 上設定屬性。

訂閱合成器事件

您可能會想要文字轉換語音處理和結果的更多深入解析。 例如,您可能想要知道合成器的啟動和停止時間,或者您可能想要知道合成期間所遇到的其他事件。

使用 SpeechSynthesizer 進行文字轉換語音時,您可以訂閱此資料表中的事件:

事件 描述 使用案例
BookmarkReached 表示已到達書籤。 若要觸發已到達書籤事件,SSML 中需要 bookmark 元素。 此事件會報告輸出音訊從開始合成到抵達 bookmark 元素所經過的時間。 事件的 Text 屬性是您在書籤的 mark 屬性中所設定的字串值。 不會說出 bookmark 元素。 您可以使用 bookmark 元素在 SSML 中插入自訂標記,以取得音訊串流中每個標記的位移。 bookmark 元素可以用來參考文字或標記順序中的特定位置。
SynthesisCanceled 表示已取消語音合成。 您可以確認合成已取消的時間。
SynthesisCompleted 表示語音合成已完成。 您可以確認合成已完成的時間。
SynthesisStarted 表示啟動語音合成。 您可以確認合成開始的時間。
Synthesizing 表示正在進行語音合成。 每次 SDK 收到來自語音服務的音訊區塊時,都會引發此事件。 您可以確認合成正在進行時間。
VisemeReceived 收到 Viseme 事件的訊號。 通常會使用發音嘴型來代表語音觀察到的關鍵姿勢。 這些關鍵姿勢包括發出特定音位時,嘴唇、下顎與舌頭的位置。 您可以使用 Viseme,以動畫顯示人物在語音音訊播放時的臉部。
WordBoundary 表示收到字邊界。 此事件會在每個新讀出字組、標點符號和句子的開頭引發。 此事件會報告目前字組從輸出音訊開頭算起的時間位移,以刻度為單位。 此事件也會在緊接於要說出之字組前的輸入文字或 SSML 中報告人物位置。 此事件常用來取得文字與對應音訊的相對位置。 您可能想要知道新的字組,然後根據時間來採取動作。 例如,您可以取得可協助您決定在說出文字時何時醒目提示字組以及持續多久的資訊。

注意

事件會在輸出音訊資料變成可用時引發,其中速度會比播放到輸出裝置還快。 呼叫端必須適當地同步串流和即時。

以下範例顯示如何訂閱事件,以進行語音合成。

重要

如果您使用 API 金鑰,請將其安全地儲存在別處,例如 Azure Key Vault。 請勿在程式碼中直接包含 API 金鑰,且切勿公開張貼金鑰。

如需 AI 服務安全性的詳細資訊,請參閱驗證對 Azure AI 服務的要求

您可以遵循快速入門中的指示,但將該 Program.cs 檔案的內容取代為下列 C# 程式碼:

using Microsoft.CognitiveServices.Speech;

class Program 
{
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    static string speechKey = Environment.GetEnvironmentVariable("SPEECH_KEY");
    static string speechRegion = Environment.GetEnvironmentVariable("SPEECH_REGION");

    async static Task Main(string[] args)
    {
        var speechConfig = SpeechConfig.FromSubscription(speechKey, speechRegion);
         
        var speechSynthesisVoiceName  = "en-US-AvaMultilingualNeural";  
        var ssml = @$"<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>
            <voice name='{speechSynthesisVoiceName}'>
                <mstts:viseme type='redlips_front'/>
                The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>.
            </voice>
        </speak>";

        // Required for sentence-level WordBoundary events
        speechConfig.SetProperty(PropertyId.SpeechServiceResponse_RequestSentenceBoundary, "true");

        using (var speechSynthesizer = new SpeechSynthesizer(speechConfig))
        {
            // Subscribe to events

            speechSynthesizer.BookmarkReached += (s, e) =>
            {
                Console.WriteLine($"BookmarkReached event:" +
                    $"\r\n\tAudioOffset: {(e.AudioOffset + 5000) / 10000}ms" +
                    $"\r\n\tText: \"{e.Text}\".");
            };

            speechSynthesizer.SynthesisCanceled += (s, e) =>
            {
                Console.WriteLine("SynthesisCanceled event");
            };

            speechSynthesizer.SynthesisCompleted += (s, e) =>
            {                
                Console.WriteLine($"SynthesisCompleted event:" +
                    $"\r\n\tAudioData: {e.Result.AudioData.Length} bytes" +
                    $"\r\n\tAudioDuration: {e.Result.AudioDuration}");
            };

            speechSynthesizer.SynthesisStarted += (s, e) =>
            {
                Console.WriteLine("SynthesisStarted event");
            };

            speechSynthesizer.Synthesizing += (s, e) =>
            {
                Console.WriteLine($"Synthesizing event:" +
                    $"\r\n\tAudioData: {e.Result.AudioData.Length} bytes");
            };

            speechSynthesizer.VisemeReceived += (s, e) =>
            {
                Console.WriteLine($"VisemeReceived event:" +
                    $"\r\n\tAudioOffset: {(e.AudioOffset + 5000) / 10000}ms" +
                    $"\r\n\tVisemeId: {e.VisemeId}");
            };

            speechSynthesizer.WordBoundary += (s, e) =>
            {
                Console.WriteLine($"WordBoundary event:" +
                    // Word, Punctuation, or Sentence
                    $"\r\n\tBoundaryType: {e.BoundaryType}" +
                    $"\r\n\tAudioOffset: {(e.AudioOffset + 5000) / 10000}ms" +
                    $"\r\n\tDuration: {e.Duration}" +
                    $"\r\n\tText: \"{e.Text}\"" +
                    $"\r\n\tTextOffset: {e.TextOffset}" +
                    $"\r\n\tWordLength: {e.WordLength}");
            };

            // Synthesize the SSML
            Console.WriteLine($"SSML to synthesize: \r\n{ssml}");
            var speechSynthesisResult = await speechSynthesizer.SpeakSsmlAsync(ssml);

            // Output the results
            switch (speechSynthesisResult.Reason)
            {
                case ResultReason.SynthesizingAudioCompleted:
                    Console.WriteLine("SynthesizingAudioCompleted result");
                    break;
                case ResultReason.Canceled:
                    var cancellation = SpeechSynthesisCancellationDetails.FromResult(speechSynthesisResult);
                    Console.WriteLine($"CANCELED: Reason={cancellation.Reason}");

                    if (cancellation.Reason == CancellationReason.Error)
                    {
                        Console.WriteLine($"CANCELED: ErrorCode={cancellation.ErrorCode}");
                        Console.WriteLine($"CANCELED: ErrorDetails=[{cancellation.ErrorDetails}]");
                        Console.WriteLine($"CANCELED: Did you set the speech resource key and region values?");
                    }
                    break;
                default:
                    break;
            }
        }

        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }
}

您可以在 GitHub 找到更多文字轉換語音範例。

使用自訂端點

自訂端點的功能與用於文字轉換語音要求的標準端點完全相同。

其中一個差異在於必須指定 EndpointId,才能透過語音 SDK 使用您的自訂語音。 您可以從文字轉換語音快速入門開始,然後使用 EndpointIdSpeechSynthesisVoiceName 更新程式碼。

var speechConfig = SpeechConfig.FromSubscription(speechKey, speechRegion);     
speechConfig.SpeechSynthesisVoiceName = "YourCustomVoiceName";
speechConfig.EndpointId = "YourEndpointId";

若要透過語音合成標記語言使用自訂語音 (SSML),請將模型名稱指定為語音名稱。 此範例使用 YourCustomVoiceName 語音。

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

執行並使用容器

語音容器會提供 Websocket 型查詢端點 API,其可透過語音 SDK 和語音 CLI 來存取。 根據預設,語音 SDK 和語音 CLI 會使用公用語音服務。 若要使用容器,您必須變更初始化方法。 使用容器主機 URL,而不是金鑰和區域。

如需關於搭配容器執行的詳細資訊,請參閱使用 Docker 安裝及執行語音容器

參考文件 | 套件 (NuGet) | GitHub 上的其他範例

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

選取合成語言和語音

語音服務中的文字轉換語音功能支援 400 種以上的語音和 140 種以上的語言和變體。 請參閱支援的文字轉換語音地區設定完整清單,或在語音資源庫中試用。

指定符合您輸入文字的 SpeechConfig 類別語言或語音,並使用指定的語音。 下列程式碼片段顯示這項技術的運作方式:

void synthesizeSpeech()
{
    auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
    // Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
    speechConfig->SetSpeechSynthesisLanguage("en-US"); 
    speechConfig->SetSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural");
}

所有神經語音都是多語系,而且可流利地使用其自己的語言和英文。 例如,如果英文的輸入文字是「I'm excited to try text to speech」,而且您設定了 es-ES-ElviraNeural,則會使用西班牙文腔調以英文讀出該文字。

如果語音不說輸入文字的語言,語音服務就不會建立合成的音訊。 如需將支援的神精語音列出的完整清單,請參閱語音服務的語言和語音支援

注意

預設語音是從語音清單 API 針對每個地區設定傳回的第一個語音。

說話語音會依下列優先順序來決定:

  • 如果您未設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會說出 en-US 的預設語音。
  • 如果您只設定 SpeechSynthesisLanguage,則說出指定地區設定的預設語音。
  • 如果同時設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會忽略 SpeechSynthesisLanguage 設定, 您使用 SpeechSynthesisVoiceName 語音指定的聲音。
  • 如果語音元素是使用語音合成標記語言 (SSML) 設定,則會忽略 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 設定。

總而言之,優先順序的順序可以描述為:

SpeechSynthesisVoiceName SpeechSynthesisLanguage SSML 結果
en-US 語音的預設聲音
所指定地區設定語音的預設聲音。
您使用 SpeechSynthesisVoiceName 語音指定的聲音。
您使用 SSML 語音指定的聲音。

將語音合成至檔案

建立 SpeechSynthesizer 物件。 下列片段顯示的物件會執行文字轉換語音並輸出至喇叭、檔案或其他輸出資料流。 SpeechSynthesizer 接受下列參數:

  • 您在上一個步驟中建立的 SpeechConfig 物件。
  • AudioConfig 物件,指定應如何處理輸出結果。
  1. 請建立 AudioConfig 執行個體,以使用 FromWavFileOutput() 函式將輸出自動寫入至 .wav 檔案:

    void synthesizeSpeech()
    {
        auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
        auto audioConfig = AudioConfig::FromWavFileOutput("path/to/write/file.wav");
    }
    
  2. 具現化 SpeechSynthesizer 執行個體。 以參數形式傳遞 speechConfig 物件和 audioConfig 物件。 若要合成語音並寫入檔案,請使用文字字串執行 SpeakTextAsync()

    void synthesizeSpeech()
    {
        auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
        auto audioConfig = AudioConfig::FromWavFileOutput("path/to/write/file.wav");
        auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig, audioConfig);
        auto result = speechSynthesizer->SpeakTextAsync("A simple test to write to a file.").get();
    }
    

當您執行程式時,這會建立合成的 .wav 檔案,該檔案會寫入您指定的位置。 此結果是最基本用法的絕佳範例。 接下來,您可以自訂輸出,以及如何將輸出回應當作記憶體內部資料流處理,以便處理自訂案例。

合成為喇叭輸出

若要將合成語音輸出到目前使用中輸出裝置 (例如喇叭),請在建立 SpeechSynthesizer 執行個體時省略 AudioConfig 參數。 以下是範例:

void synthesizeSpeech()
{
    auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
    auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);
    auto result = speechSynthesizer->SpeakTextAsync("I'm excited to try text to speech").get();
}

以記憶體內部資料流的形式取得結果

您可以使用產生的音訊資料作為記憶體內部串流,而非直接寫入至檔案。 使用記憶體內部串流,您可以建置自訂行為:

  • 將產生的位元組陣列摘要成為自訂下游服務的可搜尋資料流。
  • 將結果與其他 API 或服務整合。
  • 修改音訊資料、撰寫自訂 .wav 標頭,以及執行相關工作。

您可以對上一個範例進行這項變更。 首先,移除 AudioConfig 區塊,因為您要從這個時間點開始手動管理輸出行為,以提高掌控權。 在 SpeechSynthesizer 建構函式中針對 AudioConfig 傳遞 NULL

注意

針對 AudioConfig 傳遞 NULL (而非如同在上述喇叭輸出範例中加以省略),不會在目前作用中的輸出裝置上依預設播放音訊。

將結果儲存至 SpeechSynthesisResult 變數。 GetAudioData getter 會傳回輸出資料的 byte [] 執行個體。 您可以手動處理此 byte [] 執行個體,也可以使用 AudioDataStream 類別來管理記憶體內部資料流。

在此範例中,使用 AudioDataStream.FromResult() 靜態函式從結果中取得資料流:

void synthesizeSpeech()
{
    auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
    auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);

    auto result = speechSynthesizer->SpeakTextAsync("Getting the response as an in-memory stream.").get();
    auto stream = AudioDataStream::FromResult(result);
}

在此階段,您可使用所產生的 stream 物件來實作任何自訂行為。

自訂音訊格式

您可以自訂音訊輸出屬性,包括:

  • 音訊檔類型
  • 採樣速率
  • 位元深度

若要變更音訊格式,在 SpeechConfig 物件上使用 SetSpeechSynthesisOutputFormat() 函式。 此函式需要 SpeechSynthesisOutputFormat 類型的 enum 執行個體。 使用 enum 來選取輸出格式。 針對可用格式,請參閱音訊格式清單

根據您的需求而定,有各種選項可供不同的檔案類型使用。 依照定義,原始格式 (例如 Raw24Khz16BitMonoPcm) 不包含音訊標頭。 只有在下列其中一種情況下,才使用原始格式:

  • 您知道下游實作可以解碼原始位元資料流。
  • 您計劃根據位元深度、取樣速率和聲道數目等因素來手動建置標頭。

此範例可藉由在 SpeechConfig 物件上設定 SpeechSynthesisOutputFormat,以指定高精確度的 RIFF 格式 Riff24Khz16BitMonoPcm。 與上一節中的範例類似,您可使用 AudioDataStream 來取得結果的記憶體內部資料流,然後將其寫入至檔案。

void synthesizeSpeech()
{
    auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
    speechConfig->SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat::Riff24Khz16BitMonoPcm);

    auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);
    auto result = speechSynthesizer->SpeakTextAsync("A simple test to write to a file.").get();

    auto stream = AudioDataStream::FromResult(result);
    stream->SaveToWavFileAsync("path/to/write/file.wav").get();
}

當您執行程式時,這會將 .wav 檔案寫入指定的路徑。

使用 SSML 來自訂語音特性

您可以使用 SSML 從 XML 結構描述提交要求,以微調文字轉換語音輸出中的音調、發音、說話速度、音量和其他方面。 本節示範如何變更語音。 如需詳細資訊,請參閱語音合成標記語言概觀

若要開始使用 SSML 進行自訂,可進行細微變更來切換語音。

  1. 在根專案目錄中為 SSML 組態建立新的 XML 檔案。

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    在這裡範例中,檔案 ssml.xml。 根元素一律為 <speak>。 將文字包裝在 <voice> 元素中,可讓您使用 name 參數來變更語音。 如需支援的神經語音的完整清單,請參閱支援的語言

  2. 變更語音合成要求以參考您的 XML 檔案。 要求大多相同。 您不需要使用 SpeakTextAsync() 函式,而是使用 SpeakSsmlAsync()。 此函式需要 XML 字串。 首先,將 SSML 組態載入為字串。 從這點看來,結果物件會與先前的範例完全相同。

    void synthesizeSpeech()
    {
        auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
        auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);
    
        std::ifstream file("./ssml.xml");
        std::string ssml, line;
        while (std::getline(file, line))
        {
            ssml += line;
            ssml.push_back('\n');
        }
        auto result = speechSynthesizer->SpeakSsmlAsync(ssml).get();
    
        auto stream = AudioDataStream::FromResult(result);
        stream->SaveToWavFileAsync("path/to/write/file.wav").get();
    }
    

注意

若要在不使用 SSML 的情況下變更語音,您可以使用 SpeechConfig.SetSpeechSynthesisVoiceName("en-US-AndrewMultilingualNeural"),在 SpeechConfig 上設定屬性。

訂閱合成器事件

您可能會想要文字轉換語音處理和結果的更多深入解析。 例如,您可能想要知道合成器的啟動和停止時間,或者您可能想要知道合成期間所遇到的其他事件。

使用 SpeechSynthesizer 進行文字轉換語音時,您可以訂閱此資料表中的事件:

事件 描述 使用案例
BookmarkReached 表示已到達書籤。 若要觸發已到達書籤事件,SSML 中需要 bookmark 元素。 此事件會報告輸出音訊從開始合成到抵達 bookmark 元素所經過的時間。 事件的 Text 屬性是您在書籤的 mark 屬性中所設定的字串值。 不會說出 bookmark 元素。 您可以使用 bookmark 元素在 SSML 中插入自訂標記,以取得音訊串流中每個標記的位移。 bookmark 元素可以用來參考文字或標記順序中的特定位置。
SynthesisCanceled 表示已取消語音合成。 您可以確認合成已取消的時間。
SynthesisCompleted 表示語音合成已完成。 您可以確認合成已完成的時間。
SynthesisStarted 表示啟動語音合成。 您可以確認合成開始的時間。
Synthesizing 表示正在進行語音合成。 每次 SDK 收到來自語音服務的音訊區塊時,都會引發此事件。 您可以確認合成正在進行時間。
VisemeReceived 收到 Viseme 事件的訊號。 通常會使用發音嘴型來代表語音觀察到的關鍵姿勢。 這些關鍵姿勢包括發出特定音位時,嘴唇、下顎與舌頭的位置。 您可以使用 Viseme,以動畫顯示人物在語音音訊播放時的臉部。
WordBoundary 表示收到字邊界。 此事件會在每個新讀出字組、標點符號和句子的開頭引發。 此事件會報告目前字組從輸出音訊開頭算起的時間位移,以刻度為單位。 此事件也會在緊接於要說出之字組前的輸入文字或 SSML 中報告人物位置。 此事件常用來取得文字與對應音訊的相對位置。 您可能想要知道新的字組,然後根據時間來採取動作。 例如,您可以取得可協助您決定在說出文字時何時醒目提示字組以及持續多久的資訊。

注意

事件會在輸出音訊資料變成可用時引發,其中速度會比播放到輸出裝置還快。 呼叫端必須適當地同步串流和即時。

以下範例顯示如何訂閱事件,以進行語音合成。

重要

如果您使用 API 金鑰,請將其安全地儲存在別處,例如 Azure Key Vault。 請勿在程式碼中直接包含 API 金鑰,且切勿公開張貼金鑰。

如需 AI 服務安全性的詳細資訊,請參閱驗證對 Azure AI 服務的要求

您可以遵循快速入門中的指示,但將該 main.cpp 檔案的內容取代為下列程式碼:

#include <iostream> 
#include <stdlib.h>
#include <speechapi_cxx.h>

using namespace Microsoft::CognitiveServices::Speech;
using namespace Microsoft::CognitiveServices::Speech::Audio;

std::string getEnvironmentVariable(const char* name);

int main()
{
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    auto speechKey = getEnvironmentVariable("SPEECH_KEY");
    auto speechRegion = getEnvironmentVariable("SPEECH_REGION");

    if ((size(speechKey) == 0) || (size(speechRegion) == 0)) {
        std::cout << "Please set both SPEECH_KEY and SPEECH_REGION environment variables." << std::endl;
        return -1;
    }

    auto speechConfig = SpeechConfig::FromSubscription(speechKey, speechRegion);

    // Required for WordBoundary event sentences.
    speechConfig->SetProperty(PropertyId::SpeechServiceResponse_RequestSentenceBoundary, "true");

    const auto ssml = R"(<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>
        <voice name = 'en-US-AvaMultilingualNeural'>
            <mstts:viseme type = 'redlips_front' />
            The rainbow has seven colors : <bookmark mark = 'colors_list_begin' />Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark = 'colors_list_end' />.
        </voice>
        </speak>)";

    auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);

    // Subscribe to events

    speechSynthesizer->BookmarkReached += [](const SpeechSynthesisBookmarkEventArgs& e)
    {
        std::cout << "Bookmark reached. "
            << "\r\n\tAudioOffset: " << round(e.AudioOffset / 10000) << "ms"
            << "\r\n\tText: " << e.Text << std::endl;
    };

    speechSynthesizer->SynthesisCanceled += [](const SpeechSynthesisEventArgs& e)
    {
        std::cout << "SynthesisCanceled event" << std::endl;
    };

    speechSynthesizer->SynthesisCompleted += [](const SpeechSynthesisEventArgs& e)
    {
        auto audioDuration = std::chrono::duration_cast<std::chrono::milliseconds>(e.Result->AudioDuration).count();

        std::cout << "SynthesisCompleted event:"
            << "\r\n\tAudioData: " << e.Result->GetAudioData()->size() << "bytes"
            << "\r\n\tAudioDuration: " << audioDuration << std::endl;
    };

    speechSynthesizer->SynthesisStarted += [](const SpeechSynthesisEventArgs& e)
    {
        std::cout << "SynthesisStarted event" << std::endl;
    };

    speechSynthesizer->Synthesizing += [](const SpeechSynthesisEventArgs& e)
    {
        std::cout << "Synthesizing event:"
            << "\r\n\tAudioData: " << e.Result->GetAudioData()->size() << "bytes" << std::endl;
    };

    speechSynthesizer->VisemeReceived += [](const SpeechSynthesisVisemeEventArgs& e)
    {
        std::cout << "VisemeReceived event:"
            << "\r\n\tAudioOffset: " << round(e.AudioOffset / 10000) << "ms"
            << "\r\n\tVisemeId: " << e.VisemeId << std::endl;
    };

    speechSynthesizer->WordBoundary += [](const SpeechSynthesisWordBoundaryEventArgs& e)
    {
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(e.Duration).count();
        
        auto boundaryType = "";
        switch (e.BoundaryType) {
        case SpeechSynthesisBoundaryType::Punctuation:
            boundaryType = "Punctuation";
            break;
        case SpeechSynthesisBoundaryType::Sentence:
            boundaryType = "Sentence";
            break;
        case SpeechSynthesisBoundaryType::Word:
            boundaryType = "Word";
            break;
        }

        std::cout << "WordBoundary event:"
            // Word, Punctuation, or Sentence
            << "\r\n\tBoundaryType: " << boundaryType
            << "\r\n\tAudioOffset: " << round(e.AudioOffset / 10000) << "ms"
            << "\r\n\tDuration: " << duration
            << "\r\n\tText: \"" << e.Text << "\""
            << "\r\n\tTextOffset: " << e.TextOffset
            << "\r\n\tWordLength: " << e.WordLength << std::endl;
    };

    auto result = speechSynthesizer->SpeakSsmlAsync(ssml).get();

    // Checks result.
    if (result->Reason == ResultReason::SynthesizingAudioCompleted)
    {
        std::cout << "SynthesizingAudioCompleted result" << std::endl;
    }
    else if (result->Reason == ResultReason::Canceled)
    {
        auto cancellation = SpeechSynthesisCancellationDetails::FromResult(result);
        std::cout << "CANCELED: Reason=" << (int)cancellation->Reason << std::endl;

        if (cancellation->Reason == CancellationReason::Error)
        {
            std::cout << "CANCELED: ErrorCode=" << (int)cancellation->ErrorCode << std::endl;
            std::cout << "CANCELED: ErrorDetails=[" << cancellation->ErrorDetails << "]" << std::endl;
            std::cout << "CANCELED: Did you set the speech resource key and region values?" << std::endl;
        }
    }

    std::cout << "Press enter to exit..." << std::endl;
    std::cin.get();
}

std::string getEnvironmentVariable(const char* name)
{
#if defined(_MSC_VER)
    size_t requiredSize = 0;
    (void)getenv_s(&requiredSize, nullptr, 0, name);
    if (requiredSize == 0)
    {
        return "";
    }
    auto buffer = std::make_unique<char[]>(requiredSize);
    (void)getenv_s(&requiredSize, buffer.get(), requiredSize, name);
    return buffer.get();
#else
    auto value = getenv(name);
    return value ? value : "";
#endif
}

您可以在 GitHub 找到更多文字轉換語音範例。

使用自訂端點

自訂端點的功能與用於文字轉換語音要求的標準端點完全相同。

其中一個差異在於必須指定 EndpointId,才能透過語音 SDK 使用您的自訂語音。 您可以從文字轉換語音快速入門開始,然後使用 EndpointIdSpeechSynthesisVoiceName 更新程式碼。

auto speechConfig = SpeechConfig::FromSubscription(speechKey, speechRegion);
speechConfig->SetSpeechSynthesisVoiceName("YourCustomVoiceName");
speechConfig->SetEndpointId("YourEndpointId");

若要透過語音合成標記語言使用自訂語音 (SSML),請將模型名稱指定為語音名稱。 此範例使用 YourCustomVoiceName 語音。

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

執行並使用容器

語音容器會提供 Websocket 型查詢端點 API,其可透過語音 SDK 和語音 CLI 來存取。 根據預設,語音 SDK 和語音 CLI 會使用公用語音服務。 若要使用容器,您必須變更初始化方法。 使用容器主機 URL,而不是金鑰和區域。

如需關於搭配容器執行的詳細資訊,請參閱使用 Docker 安裝及執行語音容器

參考文件 | 套件 (Go) | GitHub 上的其他範例

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

必要條件

安裝語音 SDK

您必須先安裝適用於 Go 的語音 SDK,才能執行任何動作。

文字轉換語音到喇叭

使用下列程式碼範例,對您的預設音訊輸出裝置執行語音合成。 以您的語音金鑰和位置/區域取代變數 subscriptionregion。 執行指令碼向預設喇叭說出您的輸入文字。

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "time"

    "github.com/Microsoft/cognitive-services-speech-sdk-go/audio"
    "github.com/Microsoft/cognitive-services-speech-sdk-go/common"
    "github.com/Microsoft/cognitive-services-speech-sdk-go/speech"
)

func synthesizeStartedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Synthesis started.")
}

func synthesizingHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Printf("Synthesizing, audio chunk size %d.\n", len(event.Result.AudioData))
}

func synthesizedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Printf("Synthesized, audio length %d.\n", len(event.Result.AudioData))
}

func cancelledHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Received a cancellation.")
}

func main() {
    subscription := "YourSpeechKey"
    region := "YourSpeechRegion"

    audioConfig, err := audio.NewAudioConfigFromDefaultSpeakerOutput()
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer audioConfig.Close()
    speechConfig, err := speech.NewSpeechConfigFromSubscription(subscription, region)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechConfig.Close()
    speechSynthesizer, err := speech.NewSpeechSynthesizerFromConfig(speechConfig, audioConfig)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechSynthesizer.Close()

    speechSynthesizer.SynthesisStarted(synthesizeStartedHandler)
    speechSynthesizer.Synthesizing(synthesizingHandler)
    speechSynthesizer.SynthesisCompleted(synthesizedHandler)
    speechSynthesizer.SynthesisCanceled(cancelledHandler)

    for {
        fmt.Printf("Enter some text that you want to speak, or enter empty text to exit.\n> ")
        text, _ := bufio.NewReader(os.Stdin).ReadString('\n')
        text = strings.TrimSuffix(text, "\n")
        if len(text) == 0 {
            break
        }

        task := speechSynthesizer.SpeakTextAsync(text)
        var outcome speech.SpeechSynthesisOutcome
        select {
        case outcome = <-task:
        case <-time.After(60 * time.Second):
            fmt.Println("Timed out")
            return
        }
        defer outcome.Close()
        if outcome.Error != nil {
            fmt.Println("Got an error: ", outcome.Error)
            return
        }

        if outcome.Result.Reason == common.SynthesizingAudioCompleted {
            fmt.Printf("Speech synthesized to speaker for text [%s].\n", text)
        } else {
            cancellation, _ := speech.NewCancellationDetailsFromSpeechSynthesisResult(outcome.Result)
            fmt.Printf("CANCELED: Reason=%d.\n", cancellation.Reason)

            if cancellation.Reason == common.Error {
                fmt.Printf("CANCELED: ErrorCode=%d\nCANCELED: ErrorDetails=[%s]\nCANCELED: Did you set the speech resource key and region values?\n",
                    cancellation.ErrorCode,
                    cancellation.ErrorDetails)
            }
        }
    }
}

執行下列命令來建立 go.mod 檔案,該檔案連結至 Github 上託管的元件:

go mod init quickstart
go get github.com/Microsoft/cognitive-services-speech-sdk-go

現在建置並執行程式碼:

go build
go run quickstart

如需類別的詳細資訊,請參閱 SpeechConfigSpeechSynthesizer 參考文件。

文字轉換語音至記憶體內資料流

您可以使用產生的音訊資料作為記憶體內部串流,而非直接寫入至檔案。 使用記憶體內部串流,您可以建置自訂行為:

  • 將產生的位元組陣列摘要成為自訂下游服務的可搜尋資料流。
  • 將結果與其他 API 或服務整合。
  • 修改音訊資料、撰寫自訂 .wav 標頭,以及執行相關工作。

您可以對上一個範例進行這項變更。 移除 AudioConfig 區塊,因為您要從這個時間點開始手動管理輸出行為,以提高掌控權。 然後在 SpeechSynthesizer 建構函式中針對 AudioConfig 傳遞 nil

注意

針對 AudioConfig 傳遞 nil (而非如同在上述喇叭輸出範例中加以省略),並不會在目前作用中的輸出裝置上依預設播放音訊。

將結果儲存至 SpeechSynthesisResult 變數。 AudioData 屬性會傳回輸出資料的 []byte 執行個體。 您可以手動處理此 []byte 執行個體,也可以使用 AudioDataStream 類別來管理記憶體內部資料流。 在此範例中,您會使用 NewAudioDataStreamFromSpeechSynthesisResult() 靜態函式從結果中取得資料流。

以您的語音金鑰和位置/區域取代變數 subscriptionregion

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strings"
    "time"

    "github.com/Microsoft/cognitive-services-speech-sdk-go/speech"
)

func synthesizeStartedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Synthesis started.")
}

func synthesizingHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Printf("Synthesizing, audio chunk size %d.\n", len(event.Result.AudioData))
}

func synthesizedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Printf("Synthesized, audio length %d.\n", len(event.Result.AudioData))
}

func cancelledHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Received a cancellation.")
}

func main() {
    subscription := "YourSpeechKey"
    region := "YourSpeechRegion"

    speechConfig, err := speech.NewSpeechConfigFromSubscription(subscription, region)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechConfig.Close()
    speechSynthesizer, err := speech.NewSpeechSynthesizerFromConfig(speechConfig, nil)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechSynthesizer.Close()

    speechSynthesizer.SynthesisStarted(synthesizeStartedHandler)
    speechSynthesizer.Synthesizing(synthesizingHandler)
    speechSynthesizer.SynthesisCompleted(synthesizedHandler)
    speechSynthesizer.SynthesisCanceled(cancelledHandler)

    for {
        fmt.Printf("Enter some text that you want to speak, or enter empty text to exit.\n> ")
        text, _ := bufio.NewReader(os.Stdin).ReadString('\n')
        text = strings.TrimSuffix(text, "\n")
        if len(text) == 0 {
            break
        }

        // StartSpeakingTextAsync sends the result to channel when the synthesis starts.
        task := speechSynthesizer.StartSpeakingTextAsync(text)
        var outcome speech.SpeechSynthesisOutcome
        select {
        case outcome = <-task:
        case <-time.After(60 * time.Second):
            fmt.Println("Timed out")
            return
        }
        defer outcome.Close()
        if outcome.Error != nil {
            fmt.Println("Got an error: ", outcome.Error)
            return
        }

        // In most cases, we want to streaming receive the audio to lower the latency.
        // We can use AudioDataStream to do so.
        stream, err := speech.NewAudioDataStreamFromSpeechSynthesisResult(outcome.Result)
        defer stream.Close()
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }

        var all_audio []byte
        audio_chunk := make([]byte, 2048)
        for {
            n, err := stream.Read(audio_chunk)

            if err == io.EOF {
                break
            }

            all_audio = append(all_audio, audio_chunk[:n]...)
        }

        fmt.Printf("Read [%d] bytes from audio data stream.\n", len(all_audio))
    }
}

執行下列命令來建立 go.mod 檔案,該檔案會連結至 Github 上託管的元件:

go mod init quickstart
go get github.com/Microsoft/cognitive-services-speech-sdk-go

現在建置並執行程式碼:

go build
go run quickstart

如需類別的詳細資訊,請參閱 SpeechConfigSpeechSynthesizer 參考文件。

選取合成語言和語音

語音服務中的文字轉換語音功能支援 400 種以上的語音和 140 種以上的語言和變體。 您可以取得完整清單,或在語音資源庫中試用。

指定符合您輸入文字的 SpeechConfig 語言或語音,並使用指定的語音:

speechConfig, err := speech.NewSpeechConfigFromSubscription(key, region)
if err != nil {
    fmt.Println("Got an error: ", err)
    return
}
defer speechConfig.Close()

speechConfig.SetSpeechSynthesisLanguage("en-US")
speechConfig.SetSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural")

所有類神經語音都是多語系,而且可流利地使用其自己的語言和英文。 例如,如果英文的輸入文字是「I'm excited to try text to speech」,而且您設定了 es-ES-ElviraNeural,則會使用西班牙文腔調以英文讀出該文字。

如果語音不說輸入文字的語言,語音服務就不會建立合成的音訊。 如需將支援的神精語音列出的完整清單,請參閱語音服務的語言和語音支援

注意

預設語音是從語音清單 API 針對每個地區設定傳回的第一個語音。

說話語音會依下列優先順序來決定:

  • 如果您未設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會說出 en-US 的預設語音。
  • 如果您只設定 SpeechSynthesisLanguage,則說出指定地區設定的預設語音。
  • 如果同時設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會忽略 SpeechSynthesisLanguage 設定, 您使用 SpeechSynthesisVoiceName 語音指定的聲音。
  • 如果語音元素是使用語音合成標記語言 (SSML) 設定,則會忽略 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 設定。

總而言之,優先順序的順序可以描述為:

SpeechSynthesisVoiceName SpeechSynthesisLanguage SSML 結果
en-US 語音的預設聲音
所指定地區設定語音的預設聲音。
您使用 SpeechSynthesisVoiceName 語音指定的聲音。
您使用 SSML 語音指定的聲音。

使用 SSML 來自訂語音特性

您可以使用語音合成標記語言 (SSML) 從 XML 結構描述提交要求,以微調文字轉換語音輸出中的音調、發音、說話速度、音量等。 本節示範如何變更語音。 如需詳細資訊,請參閱語音合成標記語言概觀

若要開始使用 SSML 進行自訂,您可進行細微變更來切換語音。

首先,在根專案目錄中為 SSML 組態建立新的 XML 檔案。 在此範例中為 ssml.xml。 根元素一律為 <speak>。 將文字包裝在 <voice> 元素中,可讓您使用 name 參數來變更語音。 如需支援的神經語音的完整清單,請參閱支援的語言

<speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
  <voice name="en-US-AvaMultilingualNeural">
    When you're on the freeway, it's a good idea to use a GPS.
  </voice>
</speak>

接下來,您需要變更語音合成要求以參考您的 XML 檔案。 要求大多數相同,但您會使用 SpeakSsmlAsync(),而不是使用 SpeakTextAsync() 函式。 此函式應該有 XML 字串,因此您會先以字串形式載入 SSML 組態。 從這點看來,結果物件會與先前的範例完全相同。

注意

若要在不使用 SSML 的情況下設定語音,您可以使用 speechConfig.SetSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural"),在 SpeechConfig 上設定屬性。

訂閱合成器事件

您可能會想要文字轉換語音處理和結果的更多深入解析。 例如,您可能想要知道合成器的啟動和停止時間,或者您可能想要知道合成期間所遇到的其他事件。

使用 SpeechSynthesizer 進行文字轉換語音時,您可以訂閱此資料表中的事件:

事件 描述 使用案例
BookmarkReached 表示已到達書籤。 若要觸發已到達書籤事件,SSML 中需要 bookmark 元素。 此事件會報告輸出音訊從開始合成到抵達 bookmark 元素所經過的時間。 事件的 Text 屬性是您在書籤的 mark 屬性中所設定的字串值。 不會說出 bookmark 元素。 您可以使用 bookmark 元素在 SSML 中插入自訂標記,以取得音訊串流中每個標記的位移。 bookmark 元素可以用來參考文字或標記順序中的特定位置。
SynthesisCanceled 表示已取消語音合成。 您可以確認合成已取消的時間。
SynthesisCompleted 表示語音合成已完成。 您可以確認合成已完成的時間。
SynthesisStarted 表示啟動語音合成。 您可以確認合成開始的時間。
Synthesizing 表示正在進行語音合成。 每次 SDK 收到來自語音服務的音訊區塊時,都會引發此事件。 您可以確認合成正在進行時間。
VisemeReceived 收到 Viseme 事件的訊號。 通常會使用發音嘴型來代表語音觀察到的關鍵姿勢。 這些關鍵姿勢包括發出特定音位時,嘴唇、下顎與舌頭的位置。 您可以使用 Viseme,以動畫顯示人物在語音音訊播放時的臉部。
WordBoundary 表示收到字邊界。 此事件會在每個新讀出字組、標點符號和句子的開頭引發。 此事件會報告目前字組從輸出音訊開頭算起的時間位移,以刻度為單位。 此事件也會在緊接於要說出之字組前的輸入文字或 SSML 中報告人物位置。 此事件常用來取得文字與對應音訊的相對位置。 您可能想要知道新的字組,然後根據時間來採取動作。 例如,您可以取得可協助您決定在說出文字時何時醒目提示字組以及持續多久的資訊。

注意

事件會在輸出音訊資料變成可用時引發,其中速度會比播放到輸出裝置還快。 呼叫端必須適當地同步串流和即時。

以下範例顯示如何訂閱事件,以進行語音合成。

重要

如果您使用 API 金鑰,請將其安全地儲存在別處,例如 Azure Key Vault。 請勿在程式碼中直接包含 API 金鑰,且切勿公開張貼金鑰。

如需 AI 服務安全性的詳細資訊,請參閱驗證對 Azure AI 服務的要求

您可以遵循快速入門中的指示,但將該 speech-synthesis.go 檔案的內容取代為下列 Go 程式碼:

package main

import (
    "fmt"
    "os"
    "time"

    "github.com/Microsoft/cognitive-services-speech-sdk-go/audio"
    "github.com/Microsoft/cognitive-services-speech-sdk-go/common"
    "github.com/Microsoft/cognitive-services-speech-sdk-go/speech"
)

func bookmarkReachedHandler(event speech.SpeechSynthesisBookmarkEventArgs) {
    defer event.Close()
    fmt.Println("BookmarkReached event")
}

func synthesisCanceledHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("SynthesisCanceled event")
}

func synthesisCompletedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("SynthesisCompleted event")
    fmt.Printf("\tAudioData: %d bytes\n", len(event.Result.AudioData))
    fmt.Printf("\tAudioDuration: %d\n", event.Result.AudioDuration)
}

func synthesisStartedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("SynthesisStarted event")
}

func synthesizingHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Synthesizing event")
    fmt.Printf("\tAudioData %d bytes\n", len(event.Result.AudioData))
}

func visemeReceivedHandler(event speech.SpeechSynthesisVisemeEventArgs) {
    defer event.Close()
    fmt.Println("VisemeReceived event")
    fmt.Printf("\tAudioOffset: %dms\n", (event.AudioOffset+5000)/10000)
    fmt.Printf("\tVisemeID %d\n", event.VisemeID)
}

func wordBoundaryHandler(event speech.SpeechSynthesisWordBoundaryEventArgs) {
    defer event.Close()
    boundaryType := ""
    switch event.BoundaryType {
    case 0:
        boundaryType = "Word"
    case 1:
        boundaryType = "Punctuation"
    case 2:
        boundaryType = "Sentence"
    }
    fmt.Println("WordBoundary event")
    fmt.Printf("\tBoundaryType %v\n", boundaryType)
    fmt.Printf("\tAudioOffset: %dms\n", (event.AudioOffset+5000)/10000)
    fmt.Printf("\tDuration %d\n", event.Duration)
    fmt.Printf("\tText %s\n", event.Text)
    fmt.Printf("\tTextOffset %d\n", event.TextOffset)
    fmt.Printf("\tWordLength %d\n", event.WordLength)
}

func main() {
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    speechKey := os.Getenv("SPEECH_KEY")
    speechRegion := os.Getenv("SPEECH_REGION")

    audioConfig, err := audio.NewAudioConfigFromDefaultSpeakerOutput()
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer audioConfig.Close()
    speechConfig, err := speech.NewSpeechConfigFromSubscription(speechKey, speechRegion)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechConfig.Close()

    // Required for WordBoundary event sentences.
    speechConfig.SetProperty(common.SpeechServiceResponseRequestSentenceBoundary, "true")

    speechSynthesizer, err := speech.NewSpeechSynthesizerFromConfig(speechConfig, audioConfig)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechSynthesizer.Close()

    speechSynthesizer.BookmarkReached(bookmarkReachedHandler)
    speechSynthesizer.SynthesisCanceled(synthesisCanceledHandler)
    speechSynthesizer.SynthesisCompleted(synthesisCompletedHandler)
    speechSynthesizer.SynthesisStarted(synthesisStartedHandler)
    speechSynthesizer.Synthesizing(synthesizingHandler)
    speechSynthesizer.VisemeReceived(visemeReceivedHandler)
    speechSynthesizer.WordBoundary(wordBoundaryHandler)

    speechSynthesisVoiceName := "en-US-AvaMultilingualNeural"

    ssml := fmt.Sprintf(`<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>
            <voice name='%s'>
                <mstts:viseme type='redlips_front'/>
                The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>.
            </voice>
        </speak>`, speechSynthesisVoiceName)

    // Synthesize the SSML
    fmt.Printf("SSML to synthesize: \n\t%s\n", ssml)
    task := speechSynthesizer.SpeakSsmlAsync(ssml)

    var outcome speech.SpeechSynthesisOutcome
    select {
    case outcome = <-task:
    case <-time.After(60 * time.Second):
        fmt.Println("Timed out")
        return
    }
    defer outcome.Close()
    if outcome.Error != nil {
        fmt.Println("Got an error: ", outcome.Error)
        return
    }

    if outcome.Result.Reason == common.SynthesizingAudioCompleted {
        fmt.Println("SynthesizingAudioCompleted result")
    } else {
        cancellation, _ := speech.NewCancellationDetailsFromSpeechSynthesisResult(outcome.Result)
        fmt.Printf("CANCELED: Reason=%d.\n", cancellation.Reason)

        if cancellation.Reason == common.Error {
            fmt.Printf("CANCELED: ErrorCode=%d\nCANCELED: ErrorDetails=[%s]\nCANCELED: Did you set the speech resource key and region values?\n",
                cancellation.ErrorCode,
                cancellation.ErrorDetails)
        }
    }
}

您可以在 GitHub 找到更多文字轉換語音範例。

執行並使用容器

語音容器會提供 Websocket 型查詢端點 API,其可透過語音 SDK 和語音 CLI 來存取。 根據預設,語音 SDK 和語音 CLI 會使用公用語音服務。 若要使用容器,您必須變更初始化方法。 使用容器主機 URL,而不是金鑰和區域。

如需關於搭配容器執行的詳細資訊,請參閱使用 Docker 安裝及執行語音容器

參考文件 | GitHub 上的其他範例

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

選取合成語言和語音

語音服務中的文字轉換語音功能支援 400 種以上的語音和 140 種以上的語言和變體。 您可以取得完整清單,或在語音資源庫中試用。

指定 SpeechConfig 的語言或語音,以符合您的輸入文字並使用指定的語音。 下列程式碼片段顯示這項技術的運作方式:

public static void main(String[] args) {
    SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    // Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
    speechConfig.setSpeechSynthesisLanguage("en-US"); 
    speechConfig.setSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural");
}

所有神經語音都是多語系,而且可流利地使用其自己的語言和英文。 例如,如果英文的輸入文字是「I'm excited to try text to speech」,而且您設定了 es-ES-ElviraNeural,則會使用西班牙文腔調以英文讀出該文字。

如果語音不說輸入文字的語言,語音服務就不會建立合成的音訊。 如需將支援的神精語音列出的完整清單,請參閱語音服務的語言和語音支援

注意

預設語音是從語音清單 API 針對每個地區設定傳回的第一個語音。

說話語音會依下列優先順序來決定:

  • 如果您未設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會說出 en-US 的預設語音。
  • 如果您只設定 SpeechSynthesisLanguage,則說出指定地區設定的預設語音。
  • 如果同時設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會忽略 SpeechSynthesisLanguage 設定, 您透過 SpeechSynthesisVoiceName 語音指定的聲音。
  • 如果語音元素是使用語音合成標記語言 (SSML) 設定,則會忽略 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 設定。

總而言之,優先順序的順序可以描述為:

SpeechSynthesisVoiceName SpeechSynthesisLanguage SSML 結果
en-US 語音的預設聲音
所指定地區設定語音的預設聲音。
您使用 SpeechSynthesisVoiceName 語音指定的聲音。
您使用 SSML 語音指定的聲音。

將語音合成至檔案

建立 SpeechSynthesizer 物件。 此物件執行文字轉換語音並輸出至喇叭、檔案或其他輸出資料流。 SpeechSynthesizer 接受下列參數:

  • 您在上一個步驟中建立的 SpeechConfig 物件。
  • AudioConfig 物件,指定應如何處理輸出結果。
  1. 請建立 AudioConfig 執行個體,以使用 fromWavFileOutput() 靜態函式將輸出自動寫入至 .wav 檔案:

    public static void main(String[] args) {
        SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
        AudioConfig audioConfig = AudioConfig.fromWavFileOutput("path/to/write/file.wav");
    }
    
  2. 具現化 SpeechSynthesizer 執行個體。 以參數形式傳遞 speechConfig 物件和 audioConfig 物件。 若要合成語音並寫入檔案,請使用文字字串執行 SpeakText()

    public static void main(String[] args) {
        SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
        AudioConfig audioConfig = AudioConfig.fromWavFileOutput("path/to/write/file.wav");
    
        SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
        speechSynthesizer.SpeakText("I'm excited to try text to speech");
    }
    

當您執行程式時,這會建立合成的 .wav 檔案,該檔案會寫入您指定的位置。 此結果是最基本用法的絕佳範例。 接下來,您可以自訂輸出,以及如何將輸出回應當作記憶體內部資料流處理,以便處理自訂案例。

合成為喇叭輸出

您可能會想要文字轉換語音處理和結果的更多深入解析。 例如,您可能想要知道合成器的啟動和停止時間,或者您可能想要知道合成期間所遇到的其他事件。

若要將合成的語音輸出到目前使用中輸出裝置 (例如喇叭),請使用 fromDefaultSpeakerOutput() 靜態函數來具現化 AudioConfig。 以下是範例:

public static void main(String[] args) {
    SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    AudioConfig audioConfig = AudioConfig.fromDefaultSpeakerOutput();

    SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
    speechSynthesizer.SpeakText("I'm excited to try text to speech");
}

以記憶體內部資料流的形式取得結果

您可以使用產生的音訊資料作為記憶體內部串流,而非直接寫入至檔案。 使用記憶體內部串流,您可以建置自訂行為:

  • 將產生的位元組陣列摘要成為自訂下游服務的可搜尋資料流。
  • 將結果與其他 API 或服務整合。
  • 修改音訊資料、撰寫自訂 .wav 標頭,以及執行相關工作。

您可以對上一個範例進行這項變更。 首先,移除 AudioConfig 區塊,因為您要從這個時間點開始手動管理輸出行為,以提高掌控權。 然後在 SpeechSynthesizer 建構函式中針對 AudioConfig 傳遞 null

注意

針對 AudioConfig 傳遞 null (而非如同在上述喇叭輸出範例中加以省略),不會在目前作用中的輸出裝置上依預設播放音訊。

將結果儲存至 SpeechSynthesisResult 變數。 SpeechSynthesisResult.getAudioData() 函式會傳回輸出資料的 byte [] 執行個體。 您可以手動處理此 byte [] 執行個體,也可以使用 AudioDataStream 類別來管理記憶體內部資料流。

在此範例中,使用 AudioDataStream.fromResult() 靜態函式從結果中取得資料流:

public static void main(String[] args) {
    SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, null);

    SpeechSynthesisResult result = speechSynthesizer.SpeakText("I'm excited to try text to speech");
    AudioDataStream stream = AudioDataStream.fromResult(result);
    System.out.print(stream.getStatus());
}

在此階段,您可使用所產生的 stream 物件來實作任何自訂行為。

自訂音訊格式

您可以自訂音訊輸出屬性,包括:

  • 音訊檔類型
  • 採樣速率
  • 位元深度

若要變更音訊格式,請在 SpeechConfig 物件上使用 setSpeechSynthesisOutputFormat() 函式。 此函式需要 SpeechSynthesisOutputFormat 類型的 enum 執行個體。 使用 enum 來選取輸出格式。 針對可用格式,請參閱音訊格式清單

根據您的需求而定,有各種選項可供不同的檔案類型使用。 依照定義,原始格式 (例如 Raw24Khz16BitMonoPcm) 不包含音訊標頭。 只有在下列其中一種情況下,才使用原始格式:

  • 您知道下游實作可以解碼原始位元資料流。
  • 您計劃根據位元深度、取樣速率和聲道數目等因素來手動建置標頭。

此範例可藉由在 SpeechConfig 物件上設定 SpeechSynthesisOutputFormat,以指定高精確度的 RIFF 格式 Riff24Khz16BitMonoPcm。 與上一節中的範例類似,您可使用 AudioDataStream 來取得結果的記憶體內部資料流,然後將其寫入至檔案。

public static void main(String[] args) {
    SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");

    // set the output format
    speechConfig.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm);

    SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, null);
    SpeechSynthesisResult result = speechSynthesizer.SpeakText("I'm excited to try text to speech");
    AudioDataStream stream = AudioDataStream.fromResult(result);
    stream.saveToWavFile("path/to/write/file.wav");
}

當您執行程式時,這會將 .wav 檔案寫入指定的路徑。

使用 SSML 來自訂語音特性

您可以使用 SSML 從 XML 結構描述提交要求,以微調文字轉換語音輸出中的音調、發音、說話速度、音量和其他方面。 本節示範如何變更語音。 如需詳細資訊,請參閱 SSML 如何使用一文

若要開始使用 SSML 進行自訂,您可進行細微變更來切換語音。

  1. 在根專案目錄中為 SSML 組態建立新的 XML 檔案。

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    在這裡範例中,檔案 ssml.xml。 根元素一律為 <speak>。 將文字包裝在 <voice> 元素中,可讓您使用 name 參數來變更語音。 如需支援的神經語音的完整清單,請參閱支援的語言

  2. 變更語音合成要求以參考您的 XML 檔案。 要求大多相同。 您不需要使用 SpeakText() 函式,而是使用 SpeakSsml()。 此函式應該有 XML 字串,因此會先建立函式來載入 XML 檔案並以字串形式將其傳回:

    private static String xmlToString(String filePath) {
        File file = new File(filePath);
        StringBuilder fileContents = new StringBuilder((int)file.length());
    
        try (Scanner scanner = new Scanner(file)) {
            while(scanner.hasNextLine()) {
                fileContents.append(scanner.nextLine() + System.lineSeparator());
            }
            return fileContents.toString().trim();
        } catch (FileNotFoundException ex) {
            return "File not found.";
        }
    }
    

    在這點看來,結果物件會與先前的範例完全相同:

    public static void main(String[] args) {
        SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
        SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, null);
    
        String ssml = xmlToString("ssml.xml");
        SpeechSynthesisResult result = speechSynthesizer.SpeakSsml(ssml);
        AudioDataStream stream = AudioDataStream.fromResult(result);
        stream.saveToWavFile("path/to/write/file.wav");
    }
    

注意

若要在不使用 SSML 的情況下變更語音,使用 SpeechConfig.setSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural");,在 SpeechConfig 上設定屬性。

訂閱合成器事件

您可能會想要文字轉換語音處理和結果的更多深入解析。 例如,您可能想要知道合成器的啟動和停止時間,或者您可能想要知道合成期間所遇到的其他事件。

使用 SpeechSynthesizer 進行文字轉換語音時,您可以訂閱此資料表中的事件:

事件 描述 使用案例
BookmarkReached 表示已到達書籤。 若要觸發已到達書籤事件,SSML 中需要 bookmark 元素。 此事件會報告輸出音訊從開始合成到抵達 bookmark 元素所經過的時間。 事件的 Text 屬性是您在書籤的 mark 屬性中所設定的字串值。 不會說出 bookmark 元素。 您可以使用 bookmark 元素在 SSML 中插入自訂標記,以取得音訊串流中每個標記的位移。 bookmark 元素可以用來參考文字或標記順序中的特定位置。
SynthesisCanceled 表示已取消語音合成。 您可以確認合成已取消的時間。
SynthesisCompleted 表示語音合成已完成。 您可以確認合成已完成的時間。
SynthesisStarted 表示啟動語音合成。 您可以確認合成開始的時間。
Synthesizing 表示正在進行語音合成。 每次 SDK 收到來自語音服務的音訊區塊時,都會引發此事件。 您可以確認合成正在進行時間。
VisemeReceived 收到 Viseme 事件的訊號。 通常會使用發音嘴型來代表語音觀察到的關鍵姿勢。 這些關鍵姿勢包括發出特定音位時,嘴唇、下顎與舌頭的位置。 您可以使用 Viseme,以動畫顯示人物在語音音訊播放時的臉部。
WordBoundary 表示收到字邊界。 此事件會在每個新讀出字組、標點符號和句子的開頭引發。 此事件會報告目前字組從輸出音訊開頭算起的時間位移,以刻度為單位。 此事件也會在緊接於要說出之字組前的輸入文字或 SSML 中報告人物位置。 此事件常用來取得文字與對應音訊的相對位置。 您可能想要知道新的字組,然後根據時間來採取動作。 例如,您可以取得可協助您決定在說出文字時何時醒目提示字組以及持續多久的資訊。

注意

事件會在輸出音訊資料變成可用時引發,其中速度會比播放到輸出裝置還快。 呼叫端必須適當地同步串流和即時。

以下範例顯示如何訂閱事件,以進行語音合成。

重要

如果您使用 API 金鑰,請將其安全地儲存在別處,例如 Azure Key Vault。 請勿在程式碼中直接包含 API 金鑰,且切勿公開張貼金鑰。

如需 AI 服務安全性的詳細資訊,請參閱驗證對 Azure AI 服務的要求

您可以遵循快速入門中的指示,但將該 SpeechSynthesis.java 檔案的內容取代為下列 Java 程式碼:

import com.microsoft.cognitiveservices.speech.*;
import com.microsoft.cognitiveservices.speech.audio.*;

import java.util.Scanner;
import java.util.concurrent.ExecutionException;

public class SpeechSynthesis {
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    private static String speechKey = System.getenv("SPEECH_KEY");
    private static String speechRegion = System.getenv("SPEECH_REGION");

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        SpeechConfig speechConfig = SpeechConfig.fromSubscription(speechKey, speechRegion);
        
        // Required for WordBoundary event sentences.
        speechConfig.setProperty(PropertyId.SpeechServiceResponse_RequestSentenceBoundary, "true");

        String speechSynthesisVoiceName = "en-US-AvaMultilingualNeural"; 
        
        String ssml = String.format("<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>"
            .concat(String.format("<voice name='%s'>", speechSynthesisVoiceName))
            .concat("<mstts:viseme type='redlips_front'/>")
            .concat("The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>.")
            .concat("</voice>")
            .concat("</speak>"));

        SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig);
        {
            // Subscribe to events

            speechSynthesizer.BookmarkReached.addEventListener((o, e) -> {
                System.out.println("BookmarkReached event:");
                System.out.println("\tAudioOffset: " + ((e.getAudioOffset() + 5000) / 10000) + "ms");
                System.out.println("\tText: " + e.getText());
            });

            speechSynthesizer.SynthesisCanceled.addEventListener((o, e) -> {
                System.out.println("SynthesisCanceled event");
            });

            speechSynthesizer.SynthesisCompleted.addEventListener((o, e) -> {
                SpeechSynthesisResult result = e.getResult();                
                byte[] audioData = result.getAudioData();
                System.out.println("SynthesisCompleted event:");
                System.out.println("\tAudioData: " + audioData.length + " bytes");
                System.out.println("\tAudioDuration: " + result.getAudioDuration());
                result.close();
            });
            
            speechSynthesizer.SynthesisStarted.addEventListener((o, e) -> {
                System.out.println("SynthesisStarted event");
            });

            speechSynthesizer.Synthesizing.addEventListener((o, e) -> {
                SpeechSynthesisResult result = e.getResult();
                byte[] audioData = result.getAudioData();
                System.out.println("Synthesizing event:");
                System.out.println("\tAudioData: " + audioData.length + " bytes");
                result.close();
            });

            speechSynthesizer.VisemeReceived.addEventListener((o, e) -> {
                System.out.println("VisemeReceived event:");
                System.out.println("\tAudioOffset: " + ((e.getAudioOffset() + 5000) / 10000) + "ms");
                System.out.println("\tVisemeId: " + e.getVisemeId());
            });

            speechSynthesizer.WordBoundary.addEventListener((o, e) -> {
                System.out.println("WordBoundary event:");
                System.out.println("\tBoundaryType: " + e.getBoundaryType());
                System.out.println("\tAudioOffset: " + ((e.getAudioOffset() + 5000) / 10000) + "ms");
                System.out.println("\tDuration: " + e.getDuration());
                System.out.println("\tText: " + e.getText());
                System.out.println("\tTextOffset: " + e.getTextOffset());
                System.out.println("\tWordLength: " + e.getWordLength());
            });

            // Synthesize the SSML
            System.out.println("SSML to synthesize:");
            System.out.println(ssml);
            SpeechSynthesisResult speechSynthesisResult = speechSynthesizer.SpeakSsmlAsync(ssml).get();

            if (speechSynthesisResult.getReason() == ResultReason.SynthesizingAudioCompleted) {
                System.out.println("SynthesizingAudioCompleted result");
            }
            else if (speechSynthesisResult.getReason() == ResultReason.Canceled) {
                SpeechSynthesisCancellationDetails cancellation = SpeechSynthesisCancellationDetails.fromResult(speechSynthesisResult);
                System.out.println("CANCELED: Reason=" + cancellation.getReason());

                if (cancellation.getReason() == CancellationReason.Error) {
                    System.out.println("CANCELED: ErrorCode=" + cancellation.getErrorCode());
                    System.out.println("CANCELED: ErrorDetails=" + cancellation.getErrorDetails());
                    System.out.println("CANCELED: Did you set the speech resource key and region values?");
                }
            }
        }
        speechSynthesizer.close();

        System.exit(0);
    }
}

您可以在 GitHub 找到更多文字轉換語音範例。

使用自訂端點

自訂端點的功能與用於文字轉換語音要求的標準端點完全相同。

其中一個差異在於必須指定 EndpointId,才能透過語音 SDK 使用您的自訂語音。 您可以從文字轉換語音快速入門開始,然後使用 EndpointIdSpeechSynthesisVoiceName 更新程式碼。

SpeechConfig speechConfig = SpeechConfig.fromSubscription(speechKey, speechRegion);
speechConfig.setSpeechSynthesisVoiceName("YourCustomVoiceName");
speechConfig.setEndpointId("YourEndpointId");

若要透過語音合成標記語言使用自訂語音 (SSML),請將模型名稱指定為語音名稱。 此範例使用 YourCustomVoiceName 語音。

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

執行並使用容器

語音容器會提供 Websocket 型查詢端點 API,其可透過語音 SDK 和語音 CLI 來存取。 根據預設,語音 SDK 和語音 CLI 會使用公用語音服務。 若要使用容器,您必須變更初始化方法。 使用容器主機 URL,而不是金鑰和區域。

如需關於搭配容器執行的詳細資訊,請參閱使用 Docker 安裝及執行語音容器

參考文件 | 套件 (npm) | GitHub 上的其他範例 | 程式庫原始程式碼

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

選取合成語言和語音

語音服務中的文字轉換語音功能支援 400 種以上的語音和 140 種以上的語言和變體。 您可以取得完整清單,或在語音資源庫中試用。

指定符合您輸入文字的 SpeechConfig 語言或語音,並使用指定的語音:

function synthesizeSpeech() {
    const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    // Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
    speechConfig.speechSynthesisLanguage = "en-US"; 
    speechConfig.speechSynthesisVoiceName = "en-US-AvaMultilingualNeural";
}

synthesizeSpeech();

所有類神經語音都是多語系,而且可流利地使用其自己的語言和英文。 例如,如果英文的輸入文字是「I'm excited to try text to speech」,而且您設定了 es-ES-ElviraNeural,則會使用西班牙文腔調以英文讀出該文字。

如果語音不說輸入文字的語言,語音服務就不會建立合成的音訊。 如需將支援的神精語音列出的完整清單,請參閱語音服務的語言和語音支援

注意

預設語音是從語音清單 API 針對每個地區設定傳回的第一個語音。

說話語音會依下列優先順序來決定:

  • 如果您未設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會說出 en-US 的預設語音。
  • 如果您只設定 SpeechSynthesisLanguage,則說出指定地區設定的預設語音。
  • 如果同時設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會忽略 SpeechSynthesisLanguage 設定, 您使用 SpeechSynthesisVoiceName 語音指定的聲音。
  • 如果語音元素是使用語音合成標記語言 (SSML) 設定,則會忽略 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 設定。

總而言之,優先順序的順序可以描述為:

SpeechSynthesisVoiceName SpeechSynthesisLanguage SSML 結果
en-US 語音的預設聲音
所指定地區設定語音的預設聲音。
您使用 SpeechSynthesisVoiceName 語音指定的聲音。
您使用 SSML 語音指定的聲音。

合成文字轉換語音

若要將合成的語音輸出到目前使用中輸出裝置 (例如喇叭),請使用 fromDefaultSpeakerOutput() 靜態函數來具現化 AudioConfig。 以下是範例:

function synthesizeSpeech() {
    const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    const audioConfig = sdk.AudioConfig.fromDefaultSpeakerOutput();

    const speechSynthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
    speechSynthesizer.speakTextAsync(
        "I'm excited to try text to speech",
        result => {
            if (result) {
                speechSynthesizer.close();
                return result.audioData;
            }
        },
        error => {
            console.log(error);
            speechSynthesizer.close();
        });
}

當您執行程式時,從喇叭播放合成音訊。 此結果是最基本用法的絕佳範例。 接下來,您可以自訂輸出,以及如何將輸出回應當作記憶體內部資料流處理,以便處理自訂案例。

以記憶體內部資料流的形式取得結果

您可以使用產生的音訊資料作為記憶體內部串流,而非直接寫入至檔案。 使用記憶體內部串流,您可以建置自訂行為:

  • 將產生的位元組陣列摘要成為自訂下游服務的可搜尋資料流。
  • 將結果與其他 API 或服務整合。
  • 修改音訊資料、撰寫自訂 .wav 標頭,以及執行相關工作。

您可以對上一個範例進行這項變更。 移除 AudioConfig 區塊,因為您要從這個時間點開始手動管理輸出行為,以提高掌控權。 然後在 SpeechSynthesizer 建構函式中針對 AudioConfig 傳遞 null

注意

針對 AudioConfig 傳遞 null (而非如同在上述喇叭輸出範例中加以省略),不會在目前作用中的輸出裝置上依預設播放音訊。

將結果儲存至 SpeechSynthesisResult 變數。 SpeechSynthesisResult.audioData 屬性會傳回輸出資料的 ArrayBuffer 值,也就是預設瀏覽器資料流類型。 針對伺服器端程式碼,請將 ArrayBuffer 轉換為緩衝區資料流。

下列程式碼適用於用戶端:

function synthesizeSpeech() {
    const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    const speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig);

    speechSynthesizer.speakTextAsync(
        "I'm excited to try text to speech",
        result => {
            speechSynthesizer.close();
            return result.audioData;
        },
        error => {
            console.log(error);
            speechSynthesizer.close();
        });
}

您可使用所產生的 ArrayBuffer 物件來實作任何自訂行為。 ArrayBuffer 是要在瀏覽器中接收並從此格式播放的一般類型。

針對任何以伺服器為基礎的程式碼,如果您需要以資料流來使用資料,您必須將 ArrayBuffer 物件轉換為資料流:

function synthesizeSpeech() {
    const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    const speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig);

    speechSynthesizer.speakTextAsync(
        "I'm excited to try text to speech",
        result => {
            const { audioData } = result;

            speechSynthesizer.close();

            // convert arrayBuffer to stream
            // return stream
            const bufferStream = new PassThrough();
            bufferStream.end(Buffer.from(audioData));
            return bufferStream;
        },
        error => {
            console.log(error);
            speechSynthesizer.close();
        });
}

自訂音訊格式

您可以自訂音訊輸出屬性,包括:

  • 音訊檔類型
  • 採樣速率
  • 位元深度

若要變更音訊格式,在 SpeechConfig 物件上使用 speechSynthesisOutputFormat 屬性。 此屬性需要 SpeechSynthesisOutputFormat 類型的 enum 執行個體。 使用 enum 來選取輸出格式。 針對可用格式,請參閱音訊格式清單

根據您的需求而定,有各種選項可供不同的檔案類型使用。 依照定義,原始格式 (例如 Raw24Khz16BitMonoPcm) 不包含音訊標頭。 只有在下列其中一種情況下,才使用原始格式:

  • 您知道下游實作可以解碼原始位元資料流。
  • 您計劃根據位元深度、取樣速率和聲道數目等因素來手動建置標頭。

此範例可藉由在 SpeechConfig 物件上設定 speechSynthesisOutputFormat,以指定高精確度的 RIFF 格式 Riff24Khz16BitMonoPcm。 與上一節的範例類似,請取得音訊 ArrayBuffer 資料並與其互動。

function synthesizeSpeech() {
    const speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");

    // Set the output format
    speechConfig.speechSynthesisOutputFormat = sdk.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm;

    const speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig, null);
    speechSynthesizer.speakTextAsync(
        "I'm excited to try text to speech",
        result => {
            // Interact with the audio ArrayBuffer data
            const audioData = result.audioData;
            console.log(`Audio data byte size: ${audioData.byteLength}.`)

            speechSynthesizer.close();
        },
        error => {
            console.log(error);
            speechSynthesizer.close();
        });
}

使用 SSML 來自訂語音特性

您可以使用 SSML 從 XML 結構描述提交要求,以微調文字轉換語音輸出中的音調、發音、說話速度、音量和其他方面。 本節示範如何變更語音。 如需詳細資訊,請參閱語音合成標記語言概觀

若要開始使用 SSML 進行自訂,您可進行細微變更來切換語音。

  1. 在根專案目錄中為 SSML 組態建立新的 XML 檔案。

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    在此範例中為 ssml.xml。 根元素一律為 <speak>。 將文字包裝在 <voice> 元素中,可讓您使用 name 參數來變更語音。 如需支援的神經語音的完整清單,請參閱支援的語言

  2. 變更語音合成要求以參考您的 XML 檔案。 要求大多數相同,但您會使用 speakSsmlAsync(),而不是使用 speakTextAsync() 函式。 此函式需要 XML 字串。 建立函式以載入 XML 檔案,並以字串的形式傳回:

    function xmlToString(filePath) {
        const xml = readFileSync(filePath, "utf8");
        return xml;
    }
    

    如需 readFileSync 的詳細資訊,請參閱 Node.js 檔案系統

    結果物件會與先前的範例完全相同:

    function synthesizeSpeech() {
        const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
        const speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig, null);
    
        const ssml = xmlToString("ssml.xml");
        speechSynthesizer.speakSsmlAsync(
            ssml,
            result => {
                if (result.errorDetails) {
                    console.error(result.errorDetails);
                } else {
                    console.log(JSON.stringify(result));
                }
    
                speechSynthesizer.close();
            },
            error => {
                console.log(error);
                speechSynthesizer.close();
            });
    }
    

注意

若要在不使用 SSML 的情況下變更語音,您可以使用 SpeechConfig.speechSynthesisVoiceName = "en-US-AvaMultilingualNeural";,在 SpeechConfig 上設定屬性。

訂閱合成器事件

您可能會想要文字轉換語音處理和結果的更多深入解析。 例如,您可能想要知道合成器的啟動和停止時間,或者您可能想要知道合成期間所遇到的其他事件。

使用 SpeechSynthesizer 進行文字轉換語音時,您可以訂閱此資料表中的事件:

事件 描述 使用案例
BookmarkReached 表示已到達書籤。 若要觸發已到達書籤事件,SSML 中需要 bookmark 元素。 此事件會報告輸出音訊從開始合成到抵達 bookmark 元素所經過的時間。 事件的 Text 屬性是您在書籤的 mark 屬性中所設定的字串值。 不會說出 bookmark 元素。 您可以使用 bookmark 元素在 SSML 中插入自訂標記,以取得音訊串流中每個標記的位移。 bookmark 元素可以用來參考文字或標記順序中的特定位置。
SynthesisCanceled 表示已取消語音合成。 您可以確認合成已取消的時間。
SynthesisCompleted 表示語音合成已完成。 您可以確認合成已完成的時間。
SynthesisStarted 表示啟動語音合成。 您可以確認合成開始的時間。
Synthesizing 表示正在進行語音合成。 每次 SDK 收到來自語音服務的音訊區塊時,都會引發此事件。 您可以確認合成正在進行時間。
VisemeReceived 收到 Viseme 事件的訊號。 通常會使用發音嘴型來代表語音觀察到的關鍵姿勢。 這些關鍵姿勢包括發出特定音位時,嘴唇、下顎與舌頭的位置。 您可以使用 Viseme,以動畫顯示人物在語音音訊播放時的臉部。
WordBoundary 表示收到字邊界。 此事件會在每個新讀出字組、標點符號和句子的開頭引發。 此事件會報告目前字組從輸出音訊開頭算起的時間位移,以刻度為單位。 此事件也會在緊接於要說出之字組前的輸入文字或 SSML 中報告人物位置。 此事件常用來取得文字與對應音訊的相對位置。 您可能想要知道新的字組,然後根據時間來採取動作。 例如,您可以取得可協助您決定在說出文字時何時醒目提示字組以及持續多久的資訊。

注意

事件會在輸出音訊資料變成可用時引發,其中速度會比播放到輸出裝置還快。 呼叫端必須適當地同步串流和即時。

以下範例顯示如何訂閱事件,以進行語音合成。

重要

如果您使用 API 金鑰,請將其安全地儲存在別處,例如 Azure Key Vault。 請勿在程式碼中直接包含 API 金鑰,且切勿公開張貼金鑰。

如需 AI 服務安全性的詳細資訊,請參閱驗證對 Azure AI 服務的要求

您可以遵循快速入門中的指示,但將該 SpeechSynthesis.js 檔案的內容取代為下列 JavaScript 程式碼。

(function() {

    "use strict";

    var sdk = require("microsoft-cognitiveservices-speech-sdk");

    var audioFile = "YourAudioFile.wav";
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    const speechConfig = sdk.SpeechConfig.fromSubscription(process.env.SPEECH_KEY, process.env.SPEECH_REGION);
    const audioConfig = sdk.AudioConfig.fromAudioFileOutput(audioFile);

    var speechSynthesisVoiceName  = "en-US-AvaMultilingualNeural";  
    var ssml = `<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'> \r\n \
        <voice name='${speechSynthesisVoiceName}'> \r\n \
            <mstts:viseme type='redlips_front'/> \r\n \
            The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>. \r\n \
        </voice> \r\n \
    </speak>`;
    
    // Required for WordBoundary event sentences.
    speechConfig.setProperty(sdk.PropertyId.SpeechServiceResponse_RequestSentenceBoundary, "true");

    // Create the speech speechSynthesizer.
    var speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig);

    speechSynthesizer.bookmarkReached = function (s, e) {
        var str = `BookmarkReached event: \
            \r\n\tAudioOffset: ${(e.audioOffset + 5000) / 10000}ms \
            \r\n\tText: \"${e.text}\".`;
        console.log(str);
    };

    speechSynthesizer.synthesisCanceled = function (s, e) {
        console.log("SynthesisCanceled event");
    };
    
    speechSynthesizer.synthesisCompleted = function (s, e) {
        var str = `SynthesisCompleted event: \
                    \r\n\tAudioData: ${e.result.audioData.byteLength} bytes \
                    \r\n\tAudioDuration: ${e.result.audioDuration}`;
        console.log(str);
    };

    speechSynthesizer.synthesisStarted = function (s, e) {
        console.log("SynthesisStarted event");
    };

    speechSynthesizer.synthesizing = function (s, e) {
        var str = `Synthesizing event: \
            \r\n\tAudioData: ${e.result.audioData.byteLength} bytes`;
        console.log(str);
    };
    
    speechSynthesizer.visemeReceived = function(s, e) {
        var str = `VisemeReceived event: \
            \r\n\tAudioOffset: ${(e.audioOffset + 5000) / 10000}ms \
            \r\n\tVisemeId: ${e.visemeId}`;
        console.log(str);
    };

    speechSynthesizer.wordBoundary = function (s, e) {
        // Word, Punctuation, or Sentence
        var str = `WordBoundary event: \
            \r\n\tBoundaryType: ${e.boundaryType} \
            \r\n\tAudioOffset: ${(e.audioOffset + 5000) / 10000}ms \
            \r\n\tDuration: ${e.duration} \
            \r\n\tText: \"${e.text}\" \
            \r\n\tTextOffset: ${e.textOffset} \
            \r\n\tWordLength: ${e.wordLength}`;
        console.log(str);
    };

    // Synthesize the SSML
    console.log(`SSML to synthesize: \r\n ${ssml}`)
    console.log(`Synthesize to: ${audioFile}`);
    speechSynthesizer.speakSsmlAsync(ssml,
        function (result) {
      if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
        console.log("SynthesizingAudioCompleted result");
      } else {
        console.error("Speech synthesis canceled, " + result.errorDetails +
            "\nDid you set the speech resource key and region values?");
      }
      speechSynthesizer.close();
      speechSynthesizer = null;
    },
        function (err) {
      console.trace("err - " + err);
      speechSynthesizer.close();
      speechSynthesizer = null;
    });
}());

您可以在 GitHub 找到更多文字轉換語音範例。

執行並使用容器

語音容器會提供 Websocket 型查詢端點 API,其可透過語音 SDK 和語音 CLI 來存取。 根據預設,語音 SDK 和語音 CLI 會使用公用語音服務。 若要使用容器,您必須變更初始化方法。 使用容器主機 URL,而不是金鑰和區域。

如需關於搭配容器執行的詳細資訊,請參閱使用 Docker 安裝及執行語音容器

參考文件 | 套件 (下載) | GitHub 上的其他範例

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

必要條件

安裝語音 SDK 和範例

Azure-Samples/cognitive-services-speech-sdk 存放庫會包含以 Objective-C 撰寫的範例,適用於 iOS 和 Mac。 選取連結即可查看每個範例的安裝指示:

使用自訂端點

自訂端點的功能與用於文字轉換語音要求的標準端點完全相同。

其中一個差異在於必須指定 EndpointId,才能透過語音 SDK 使用您的自訂語音。 您可以從文字轉換語音快速入門開始,然後使用 EndpointIdSpeechSynthesisVoiceName 更新程式碼。

SPXSpeechConfiguration *speechConfig = [[SPXSpeechConfiguration alloc] initWithSubscription:speechKey region:speechRegion];
speechConfig.speechSynthesisVoiceName = @"YourCustomVoiceName";
speechConfig.EndpointId = @"YourEndpointId";

若要透過語音合成標記語言使用自訂語音 (SSML),請將模型名稱指定為語音名稱。 此範例使用 YourCustomVoiceName 語音。

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

執行並使用容器

語音容器會提供 Websocket 型查詢端點 API,其可透過語音 SDK 和語音 CLI 來存取。 根據預設,語音 SDK 和語音 CLI 會使用公用語音服務。 若要使用容器,您必須變更初始化方法。 使用容器主機 URL,而不是金鑰和區域。

如需關於搭配容器執行的詳細資訊,請參閱使用 Docker 安裝及執行語音容器

參考文件 | 套件 (下載) | GitHub 上的其他範例

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

必要條件

安裝語音 SDK 和範例

Azure-Samples/cognitive-services-speech-sdk 存放庫會包含以 Swift 撰寫的範例,適用於 iOS 和 Mac。 選取連結即可查看每個範例的安裝指示:

執行並使用容器

語音容器會提供 Websocket 型查詢端點 API,其可透過語音 SDK 和語音 CLI 來存取。 根據預設,語音 SDK 和語音 CLI 會使用公用語音服務。 若要使用容器,您必須變更初始化方法。 使用容器主機 URL,而不是金鑰和區域。

如需關於搭配容器執行的詳細資訊,請參閱使用 Docker 安裝及執行語音容器

參考文件 | 套件 (PyPi) | GitHub 上的其他範例

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

選取合成語言和語音

語音服務中的文字轉換語音功能支援 400 種以上的語音和 140 種以上的語言和變體。 您可以取得完整清單,或在語音資源庫中試用。

指定符合您輸入文字的 SpeechConfig 語言或語音,並使用指定的語音:

# Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
speech_config.speech_synthesis_language = "en-US" 
speech_config.speech_synthesis_voice_name ="en-US-AvaMultilingualNeural"

所有類神經語音都是多語系,而且可流利地使用其自己的語言和英文。 例如,如果英文的輸入文字是「I'm excited to try text to speech」,而且您設定了 es-ES-ElviraNeural,則會使用西班牙文腔調以英文讀出該文字。

如果語音不說輸入文字的語言,語音服務就不會建立合成的音訊。 如需將支援的神精語音列出的完整清單,請參閱語音服務的語言和語音支援

注意

預設語音是從語音清單 API 針對每個地區設定傳回的第一個語音。

說話語音會依下列優先順序來決定:

  • 如果您未設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會說出 en-US 的預設語音。
  • 如果您只設定 SpeechSynthesisLanguage,則說出指定地區設定的預設語音。
  • 如果同時設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則會忽略 SpeechSynthesisLanguage 設定, 您使用 SpeechSynthesisVoiceName 語音指定的聲音。
  • 如果語音元素是使用語音合成標記語言 (SSML) 設定,則會忽略 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 設定。

總而言之,優先順序的順序可以描述為:

SpeechSynthesisVoiceName SpeechSynthesisLanguage SSML 結果
en-US 語音的預設聲音
所指定地區設定語音的預設聲音。
您使用 SpeechSynthesisVoiceName 語音指定的聲音。
您使用 SSML 語音指定的聲音。

將語音合成至檔案

建立 SpeechSynthesizer 物件。 此物件執行文字轉換語音並輸出至喇叭、檔案或其他輸出資料流。 SpeechSynthesizer 接受下列參數:

  1. 請建立 AudioOutputConfig 執行個體,以使用 filename 建構函式參數將輸出自動寫入至 .wav 檔案:

    audio_config = speechsdk.audio.AudioOutputConfig(filename="path/to/write/file.wav")
    
  2. 以參數形式傳遞 speech_config 物件和 audio_config 物件來具現化 SpeechSynthesizer。 若要合成語音並寫入檔案,請使用文字字串執行 speak_text_async()

    speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)
    speech_synthesis_result = speech_synthesizer.speak_text_async("I'm excited to try text to speech").get()
    
    

當您執行程式時,這會建立合成的 .wav 檔案,該檔案會寫入您指定的位置。 此結果是最基本用法的絕佳範例。 接下來,您可以自訂輸出,以及如何將輸出回應當作記憶體內部資料流處理,以便處理自訂案例。

合成為喇叭輸出

若要將合成語音輸出到目前使用中輸出裝置 (例如喇叭),請在建立 AudioOutputConfig 執行個體時設定 use_default_speaker 參數。 以下是範例:

audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=True)

以記憶體內部資料流的形式取得結果

您可以使用產生的音訊資料作為記憶體內部串流,而非直接寫入至檔案。 使用記憶體內部串流,您可以建置自訂行為:

  • 將產生的位元組陣列摘要成為自訂下游服務的可搜尋資料流。
  • 將結果與其他 API 或服務整合。
  • 修改音訊資料、撰寫自訂 .wav 標頭,以及執行相關工作。

您可以對上一個範例進行這項變更。 首先,移除 AudioConfig,因為您會從這個時間點開始手動管理輸出行為,以提高掌控權。 在 SpeechSynthesizer 建構函式中針對 AudioConfig 傳遞 None

注意

針對 AudioConfig 傳遞 None (而非如同在上述喇叭輸出範例中加以省略),不會在目前作用中的輸出裝置上依預設播放音訊。

將結果儲存至 SpeechSynthesisResult 變數。 audio_data 屬性包含輸出資料的 bytes 物件。 您可以手動處理此物件,也可以使用 AudioDataStream 類別來管理記憶體內部資料流。

在此範例中,使用 AudioDataStream 建構函式,從結果中取得資料流:

speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=None)
speech_synthesis_result = speech_synthesizer.speak_text_async("I'm excited to try text to speech").get()
stream = speechsdk.AudioDataStream(speech_synthesis_result)

在此階段,您可使用所產生的 stream 物件來實作任何自訂行為。

自訂音訊格式

您可以自訂音訊輸出屬性,包括:

  • 音訊檔類型
  • 採樣速率
  • 位元深度

若要變更音訊格式,在 SpeechConfig 物件上使用 set_speech_synthesis_output_format() 函式。 此函式需要 SpeechSynthesisOutputFormat 類型的 enum 執行個體。 使用 enum 來選取輸出格式。 針對可用格式,請參閱音訊格式清單

根據您的需求而定,有各種選項可供不同的檔案類型使用。 依照定義,原始格式 (例如 Raw24Khz16BitMonoPcm) 不包含音訊標頭。 只有在下列其中一種情況下,才使用原始格式:

  • 您知道下游實作可以解碼原始位元資料流。
  • 您計劃根據位元深度、取樣速率和聲道數目等因素來手動建置標頭。

此範例可藉由在 SpeechConfig 物件上設定 SpeechSynthesisOutputFormat,以指定高精確度的 RIFF 格式 Riff24Khz16BitMonoPcm。 與上一節中的範例類似,您可使用 AudioDataStream 來取得結果的記憶體內部資料流,然後將其寫入至檔案。

speech_config.set_speech_synthesis_output_format(speechsdk.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm)
speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=None)

speech_synthesis_result = speech_synthesizer.speak_text_async("I'm excited to try text to speech").get()
stream = speechsdk.AudioDataStream(speech_synthesis_result)
stream.save_to_wav_file("path/to/write/file.wav")

當您執行程式時,這會將 .wav 檔案寫入指定的路徑。

使用 SSML 來自訂語音特性

您可以使用 SSML 從 XML 結構描述提交要求,以微調文字轉換語音輸出中的音調、發音、說話速度、音量和其他方面。 本節示範如何變更語音。 如需詳細資訊,請參閱語音合成標記語言概觀

若要開始使用 SSML 進行自訂,可進行細微變更來切換語音。

  1. 在根專案目錄中為 SSML 組態建立新的 XML 檔案。

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    在這裡範例中,檔案 ssml.xml。 根元素一律為 <speak>。 將文字包裝在 <voice> 元素中,可讓您使用 name 參數來變更語音。 如需支援的神經語音的完整清單,請參閱支援的語言

  2. 變更語音合成要求以參考您的 XML 檔案。 要求大多相同。 不需要使用 speak_text_async() 函式,而是使用 speak_ssml_async()。 此函式需要 XML 字串。 首先將 SSML 組態載入為字串。 從這點看來,結果物件會與先前的範例完全相同。

    注意

    如果 ssml_string 在字串開頭包含 ,則必須去除 BOM 格式,否則服務會傳回錯誤。 若要這麼做,請如下所示設定 encoding 參數:open("ssml.xml", "r", encoding="utf-8-sig")

    speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=None)
    
    ssml_string = open("ssml.xml", "r").read()
    speech_synthesis_result = speech_synthesizer.speak_ssml_async(ssml_string).get()
    
    stream = speechsdk.AudioDataStream(speech_synthesis_result)
    stream.save_to_wav_file("path/to/write/file.wav")
    

注意

若要在不使用 SSML 的情況下變更語音,您可以使用 speech_config.speech_synthesis_voice_name = "en-US-AvaMultilingualNeural",在 SpeechConfig 上設定屬性。

訂閱合成器事件

您可能會想要文字轉換語音處理和結果的更多深入解析。 例如,您可能想要知道合成器的啟動和停止時間,或者您可能想要知道合成期間所遇到的其他事件。

使用 SpeechSynthesizer 進行文字轉換語音時,您可以訂閱此資料表中的事件:

事件 描述 使用案例
BookmarkReached 表示已到達書籤。 若要觸發已到達書籤事件,SSML 中需要 bookmark 元素。 此事件會報告輸出音訊從開始合成到抵達 bookmark 元素所經過的時間。 事件的 Text 屬性是您在書籤的 mark 屬性中所設定的字串值。 不會說出 bookmark 元素。 您可以使用 bookmark 元素在 SSML 中插入自訂標記,以取得音訊串流中每個標記的位移。 bookmark 元素可以用來參考文字或標記順序中的特定位置。
SynthesisCanceled 表示已取消語音合成。 您可以確認合成已取消的時間。
SynthesisCompleted 表示語音合成已完成。 您可以確認合成已完成的時間。
SynthesisStarted 表示啟動語音合成。 您可以確認合成開始的時間。
Synthesizing 表示正在進行語音合成。 每次 SDK 收到來自語音服務的音訊區塊時,都會引發此事件。 您可以確認合成正在進行時間。
VisemeReceived 收到 Viseme 事件的訊號。 通常會使用發音嘴型來代表語音觀察到的關鍵姿勢。 這些關鍵姿勢包括發出特定音位時,嘴唇、下顎與舌頭的位置。 您可以使用 Viseme,以動畫顯示人物在語音音訊播放時的臉部。
WordBoundary 表示收到字邊界。 此事件會在每個新讀出字組、標點符號和句子的開頭引發。 此事件會報告目前字組從輸出音訊開頭算起的時間位移,以刻度為單位。 此事件也會在緊接於要說出之字組前的輸入文字或 SSML 中報告人物位置。 此事件常用來取得文字與對應音訊的相對位置。 您可能想要知道新的字組,然後根據時間來採取動作。 例如,您可以取得可協助您決定在說出文字時何時醒目提示字組以及持續多久的資訊。

注意

事件會在輸出音訊資料變成可用時引發,其中速度會比播放到輸出裝置還快。 呼叫端必須適當地同步串流和即時。

以下範例顯示如何訂閱事件,以進行語音合成。

重要

如果您使用 API 金鑰,請將其安全地儲存在別處,例如 Azure Key Vault。 請勿在程式碼中直接包含 API 金鑰,且切勿公開張貼金鑰。

如需 AI 服務安全性的詳細資訊,請參閱驗證對 Azure AI 服務的要求

您可以遵循快速入門中的指示,但將該 speech-synthesis.py 檔案的內容取代為下列 Python 程式碼:

import os
import azure.cognitiveservices.speech as speechsdk

def speech_synthesizer_bookmark_reached_cb(evt: speechsdk.SessionEventArgs):
    print('BookmarkReached event:')
    print('\tAudioOffset: {}ms'.format((evt.audio_offset + 5000) / 10000))
    print('\tText: {}'.format(evt.text))

def speech_synthesizer_synthesis_canceled_cb(evt: speechsdk.SessionEventArgs):
    print('SynthesisCanceled event')

def speech_synthesizer_synthesis_completed_cb(evt: speechsdk.SessionEventArgs):
    print('SynthesisCompleted event:')
    print('\tAudioData: {} bytes'.format(len(evt.result.audio_data)))
    print('\tAudioDuration: {}'.format(evt.result.audio_duration))

def speech_synthesizer_synthesis_started_cb(evt: speechsdk.SessionEventArgs):
    print('SynthesisStarted event')

def speech_synthesizer_synthesizing_cb(evt: speechsdk.SessionEventArgs):
    print('Synthesizing event:')
    print('\tAudioData: {} bytes'.format(len(evt.result.audio_data)))

def speech_synthesizer_viseme_received_cb(evt: speechsdk.SessionEventArgs):
    print('VisemeReceived event:')
    print('\tAudioOffset: {}ms'.format((evt.audio_offset + 5000) / 10000))
    print('\tVisemeId: {}'.format(evt.viseme_id))

def speech_synthesizer_word_boundary_cb(evt: speechsdk.SessionEventArgs):
    print('WordBoundary event:')
    print('\tBoundaryType: {}'.format(evt.boundary_type))
    print('\tAudioOffset: {}ms'.format((evt.audio_offset + 5000) / 10000))
    print('\tDuration: {}'.format(evt.duration))
    print('\tText: {}'.format(evt.text))
    print('\tTextOffset: {}'.format(evt.text_offset))
    print('\tWordLength: {}'.format(evt.word_length))

# This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
speech_config = speechsdk.SpeechConfig(subscription=os.environ.get('SPEECH_KEY'), region=os.environ.get('SPEECH_REGION'))

# Required for WordBoundary event sentences.
speech_config.set_property(property_id=speechsdk.PropertyId.SpeechServiceResponse_RequestSentenceBoundary, value='true')

audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=True)
speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)

# Subscribe to events
speech_synthesizer.bookmark_reached.connect(speech_synthesizer_bookmark_reached_cb)
speech_synthesizer.synthesis_canceled.connect(speech_synthesizer_synthesis_canceled_cb)
speech_synthesizer.synthesis_completed.connect(speech_synthesizer_synthesis_completed_cb)
speech_synthesizer.synthesis_started.connect(speech_synthesizer_synthesis_started_cb)
speech_synthesizer.synthesizing.connect(speech_synthesizer_synthesizing_cb)
speech_synthesizer.viseme_received.connect(speech_synthesizer_viseme_received_cb)
speech_synthesizer.synthesis_word_boundary.connect(speech_synthesizer_word_boundary_cb)

# The language of the voice that speaks.
speech_synthesis_voice_name='en-US-AvaMultilingualNeural'

ssml = """<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>
    <voice name='{}'>
        <mstts:viseme type='redlips_front'/>
        The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>.
    </voice>
</speak>""".format(speech_synthesis_voice_name)

# Synthesize the SSML
print("SSML to synthesize: \r\n{}".format(ssml))
speech_synthesis_result = speech_synthesizer.speak_ssml_async(ssml).get()

if speech_synthesis_result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
    print("SynthesizingAudioCompleted result")
elif speech_synthesis_result.reason == speechsdk.ResultReason.Canceled:
    cancellation_details = speech_synthesis_result.cancellation_details
    print("Speech synthesis canceled: {}".format(cancellation_details.reason))
    if cancellation_details.reason == speechsdk.CancellationReason.Error:
        if cancellation_details.error_details:
            print("Error details: {}".format(cancellation_details.error_details))
            print("Did you set the speech resource key and region values?")

您可以在 GitHub 找到更多文字轉換語音範例。

使用自訂端點

自訂端點的功能與用於文字轉換語音要求的標準端點完全相同。

其中一個差異在於必須指定 endpoint_id,才能透過語音 SDK 使用您的自訂語音。 您可以從文字轉換語音快速入門開始,然後使用 endpoint_idspeech_synthesis_voice_name 更新程式碼。

speech_config = speechsdk.SpeechConfig(subscription=os.environ.get('SPEECH_KEY'), region=os.environ.get('SPEECH_REGION'))
speech_config.endpoint_id = "YourEndpointId"
speech_config.speech_synthesis_voice_name = "YourCustomVoiceName"

若要透過語音合成標記語言使用自訂語音 (SSML),請將模型名稱指定為語音名稱。 此範例使用 YourCustomVoiceName 語音。

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

執行並使用容器

語音容器會提供 Websocket 型查詢端點 API,其可透過語音 SDK 和語音 CLI 來存取。 根據預設,語音 SDK 和語音 CLI 會使用公用語音服務。 若要使用容器,您必須變更初始化方法。 使用容器主機 URL,而不是金鑰和區域。

如需關於搭配容器執行的詳細資訊,請參閱使用 Docker 安裝及執行語音容器

語音轉換文字 REST API 參考 | 適用於簡短音訊的語音轉換文字 REST API 參考 | GitHub 上的其他範例

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

必要條件

將文字轉換成語音

在命令提示字元中,執行下列命令。 將這些值插入命令:

  • 您的語音資源金鑰
  • 您的語音資源區域

您也可能想要變更下列值:

  • X-Microsoft-OutputFormat 標頭值,可控制音訊輸出格式。 您可以在文字轉換語音 REST API 參考中找到支援的音訊輸出格式清單。
  • 輸出語音。 若要取得語音服務端點可用的語音清單,請參閱語音清單 API
  • 輸出檔案。 在此範例中,我們會將伺服器的回應導向至名為 output.mp3 的檔案。
curl --location --request POST 'https://YOUR_RESOURCE_REGION.tts.speech.microsoft.com/cognitiveservices/v1' \
--header 'Ocp-Apim-Subscription-Key: YOUR_RESOURCE_KEY' \
--header 'Content-Type: application/ssml+xml' \
--header 'X-Microsoft-OutputFormat: audio-16khz-128kbitrate-mono-mp3' \
--header 'User-Agent: curl' \
--data-raw '<speak version='\''1.0'\'' xml:lang='\''en-US'\''>
    <voice name='\''en-US-AvaMultilingualNeural'\''>
        I am excited to try text to speech
    </voice>
</speak>' > output.mp3

在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。

如需下列區域的詳細資訊,請參閱什麼是文字轉換語音?

  • 以記憶體內部資料流的形式取得回應。
  • 自訂輸出採樣速率和位元速率。
  • 使用語音合成標記語言 (SSML) 提交合成要求。
  • 使用神經語音。
  • 訂閱事件,並針對結果採取動作。

必要條件

下載並安裝

請遵循下列步驟,並參閱語音 CLI 快速入門,以了解平台的其他需求。

  1. 執行下列 .NET CLI 命令以安裝 Speech CLI:

    dotnet tool install --global Microsoft.CognitiveServices.Speech.CLI
    
  2. 執行下列命令來設定您的語音資源金鑰和區域。 以您的語音資源金鑰取代 SUBSCRIPTION-KEY,而以您的語音資源區域取代 REGION

    spx config @key --set SUBSCRIPTION-KEY
    spx config @region --set REGION
    

將語音合成至喇叭

現在您已準備好執行語音 CLI,從文字合成語音。

  • 在主控台視窗,變更為包含語音 CLI 二進位檔案的目錄。 然後執行下列命令:

    spx synthesize --text "I'm excited to try text to speech"
    

語音 CLI 會透過電腦喇叭以英文產生自然語言。

將語音合成至檔案

  • 執行下列命令,將說話者的輸出變更為 .wav 檔案:

    spx synthesize --text "I'm excited to try text to speech" --audio output greetings.wav
    

語音 CLI 在 greetings.wav 音訊檔案中以英文產生自然語言。

執行並使用容器

語音容器會提供 Websocket 型查詢端點 API,其可透過語音 SDK 和語音 CLI 來存取。 根據預設,語音 SDK 和語音 CLI 會使用公用語音服務。 若要使用容器,您必須變更初始化方法。 使用容器主機 URL,而不是金鑰和區域。

如需關於搭配容器執行的詳細資訊,請參閱使用 Docker 安裝及執行語音容器

下一步