如何從文字合成語音

參考文件 | 套件 (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";
}

所有神經語音都是多語系的,並且以自己的語言和英語流暢。 例如,如果以英文輸入文字,則為「我很高興嘗試文字到語音轉換」,而您選取 es-ES-ElviraNeural時,文字會以西班牙文口音說出。

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

注意

默認語音是每個地區設定從 語音清單 API 傳回的第一個語音。

說話的聲音會依優先順序決定,如下所示:

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

將語音合成至檔案

建立 SpeechSynthesizer 物件。 下列代碼段所示的這個物件會執行文字到語音轉換,以及輸出到說話者、檔案或其他輸出數據流。 SpeechSynthesizer 接受 做為參數:

  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此內容中的語句會自動處置 Unmanaged 資源,並導致對象在處置之後超出範圍。

  2. 使用另一個 using 語句具SpeechSynthesizer現化實例。 將物件 speechConfigaudioConfig 對象當做參數傳遞。 若要合成語音並寫入檔案,請使用文字字串執行 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 區塊,因為您從這個點開始手動管理輸出行為,以增加控制。 AudioConfig傳入 null 建構函式中的 SpeechSynthesizer

注意

針對 nullAudioConfig傳遞 ,而不是省略在先前的喇叭輸出範例中,預設不會在目前的使用中輸出裝置上播放音訊。

將結果儲存至 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 對象來實作任何自定義行為。

自訂音訊格式

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

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

若要變更音訊格式,您可以在 物件上使用 SpeechConfigSetSpeechSynthesisOutputFormat()式。 此函式需要 enum SpeechSynthesisOutputFormat類型的實例。 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 檔案。 要求大多相同,但不是使用 SpeakTextAsync() 函式,而是使用 SpeakSsmlAsync()。 此函式需要 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 的情況下變更語音,您可以使用 在 上SpeechConfigSpeechConfig.SpeechSynthesisVoiceName = "en-US-AvaMultilingualNeural";設定 屬性。

訂閱合成器事件

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

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

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

注意

當輸出音訊數據可供使用時,就會引發事件,這比播放輸出裝置更快。 呼叫端必須適當地同步串流和即時。

以下是示範如何訂閱事件以進行語音合成的範例。 您可以遵循快速入門中的指示,但以下列 C# 程式代碼取代該Program.cs檔案的內容:

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 使用您的自訂語音。 您可以從文字到語音轉換快速入門開始,然後使用 和SpeechSynthesisVoiceName更新程序代碼EndpointId

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");
}

所有神經語音都是多語系的,並且以自己的語言和英語流暢。 例如,如果英文輸入文字是「我很高興能嘗試文字到語音轉換」,而您選取 es-ES-ElviraNeural時,文字會以西班牙文口音說出。

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

注意

默認語音是每個地區設定從 語音清單 API 傳回的第一個語音。

說話的聲音會依優先順序決定,如下所示:

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

將語音合成至檔案

建立 SpeechSynthesizer 物件。 下列代碼段所示的這個物件會執行文字到語音轉換,以及輸出到說話者、檔案或其他輸出數據流。 SpeechSynthesizer 接受 做為參數:

  1. 建立 AudioConfig 實體,以使用 FromWavFileOutput() 函式自動將輸出寫入.wav檔案:

    void synthesizeSpeech()
    {
        auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
        auto audioConfig = AudioConfig::FromWavFileOutput("path/to/write/file.wav");
    }
    
  2. 具現化 SpeechSynthesizer 實例。 將物件 speechConfigaudioConfig 對象當做參數傳遞。 若要合成語音並寫入檔案,請使用文字字串執行 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 區塊,因為您從這個點開始手動管理輸出行為,以增加控制。 AudioConfig傳入 NULL 建構函式中的 SpeechSynthesizer

注意

針對 NULLAudioConfig傳遞 ,而不是省略在先前的喇叭輸出範例中,預設不會在目前的使用中輸出裝置上播放音訊。

將結果儲存至 SpeechSynthesisResult 變數。 getter 會GetAudioDatabyte []傳回輸出數據的實例。 您可以手動使用此 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 對象來實作任何自定義行為。

自訂音訊格式

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

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

若要變更音訊格式,請使用 SetSpeechSynthesisOutputFormat() 物件上的 SpeechConfig 函式。 此函式需要 enum SpeechSynthesisOutputFormat類型的實例。 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 的情況下變更語音,您可以使用 在 上SpeechConfigSpeechConfig.SetSpeechSynthesisVoiceName("en-US-AndrewMultilingualNeural")設定 屬性。

訂閱合成器事件

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

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

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

注意

當輸出音訊數據可供使用時,就會引發事件,這比播放輸出裝置更快。 呼叫端必須適當地同步串流和即時。

以下是示範如何訂閱事件以進行語音合成的範例。 您可以遵循快速入門中的指示,但以下列程式代碼取代該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 使用您的自訂語音。 您可以從文字到語音轉換快速入門開始,然後使用 和SpeechSynthesisVoiceName更新程序代碼EndpointId

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)。
  • 使用神經語音。
  • 訂閱事件並針對結果採取行動。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站上建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,請選取 [移至資源 ] 以檢視和管理密鑰。 如需 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

安裝語音 SDK

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

文字轉換語音到喇叭

使用下列程式代碼範例,對預設音訊輸出裝置執行語音合成。 將變數 subscription 取代 region 為您的語音金鑰和位置/區域。 執行文本會將您的輸入文字讀出默認說話者。

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傳遞 nilAudioConfig

注意

nilAudioConfig針對 傳遞 ,而不是省略上一個喇叭輸出範例中所做的,預設不會在目前的使用中輸出裝置上播放音訊。

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

將變數 subscription 取代 region 為您的語音金鑰和位置/區域:

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")

所有神經語音都是多語系的,並且以自己的語言和英語流暢。 例如,如果英文輸入文字是「我很高興能嘗試文字到語音轉換」,而您選取 es-ES-ElviraNeural時,文字會以西班牙文口音說出。

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

注意

默認語音是每個地區設定從 語音清單 API 傳回的第一個語音。

說話的聲音會依優先順序決定,如下所示:

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

使用 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 檔案。 要求大多相同,但不是使用 SpeakTextAsync() 函式,而是使用 SpeakSsmlAsync()。 此函式需要 XML 字串,因此您必須先將 SSML 組態載入為字串。 此時,結果物件與先前的範例完全相同。

注意

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

訂閱合成器事件

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

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

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

注意

當輸出音訊數據可供使用時,就會引發事件,這比播放輸出裝置更快。 呼叫端必須適當地同步串流和即時。

以下是示範如何訂閱事件以進行語音合成的範例。 您可以遵循快速入門中的指示,但以下列 Go 程式代碼取代該speech-synthesis.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");
}

所有神經語音都是多語系的,並且以自己的語言和英語流暢。 例如,如果英文輸入文字是「我很高興能嘗試文字到語音轉換」,而您選取 es-ES-ElviraNeural時,文字會以西班牙文口音說出。

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

注意

默認語音是每個地區設定從 語音清單 API 傳回的第一個語音。

說話的聲音會依優先順序決定,如下所示:

  • 如果您未設定 SpeechSynthesisVoiceNameSpeechSynthesisLanguage,則表示 en-US 的預設語音。
  • 如果您只設定 SpeechSynthesisLanguage,則指定地區設定的預設語音會說話。
  • 如果 同時 SpeechSynthesisVoiceName 設定 和 SpeechSynthesisLanguage ,則會 SpeechSynthesisLanguage 忽略設定。 您使用 SpeechSynthesisVoiceName 說話所指定的語音。
  • 如果使用語音合成標記語言 (SSML) 設定語音專案,SpeechSynthesisVoiceName則會忽略 和 SpeechSynthesisLanguage 設定。

將語音合成至檔案

建立 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 實例。 將物件 speechConfigaudioConfig 對象當做參數傳遞。 若要合成語音並寫入檔案,請使用文字字串執行 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傳遞 nullAudioConfig

注意

nullAudioConfig針對 傳遞 ,而不是省略上一個喇叭輸出範例中所做的,預設不會在目前的使用中輸出裝置上播放音訊。

將結果儲存至 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 對象來實作任何自定義行為。

自訂音訊格式

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

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

若要變更音訊格式,您可以在 物件上使用 SpeechConfigsetSpeechSynthesisOutputFormat()式。 此函式需要 enum SpeechSynthesisOutputFormat類型的實例。 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 事件的訊號。 Visemes 通常用來代表觀察語音中的關鍵姿勢。 關鍵姿勢包括嘴唇、下巴和舌頭在產生特定音素時的位置。 您可以使用 visemes,在語音音訊播放時以動畫顯示字元的臉部。
WordBoundary 收到字邊界的訊號。 這個事件會在每個新的口語字、標點符號和句子的開頭引發。 事件會報告目前文字的時間位移,以刻度為單位,從輸出音訊的開頭。 此事件也會報告輸入文字或 SSML 中的字元位置,緊接在即將說話的文字之前。 此事件通常用來取得文字和對應音訊的相對位置。 您可能想要知道一個新字,然後根據時間採取動作。 例如,您可以取得可協助您決定文字說話的時間和時間長度的資訊。

注意

當輸出音訊數據可供使用時,就會引發事件,這比播放輸出裝置更快。 呼叫端必須適當地同步串流和即時。

以下是示範如何訂閱事件以進行語音合成的範例。 您可以遵循快速入門中的指示,但以下列 Java 程式代碼取代該SpeechSynthesis.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 使用您的自訂語音。 您可以從文字到語音轉換快速入門開始,然後使用 和SpeechSynthesisVoiceName更新程序代碼EndpointId

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();

所有神經語音都是多語系的,並且以自己的語言和英語流暢。 例如,如果英文輸入文字是「我很高興能嘗試文字到語音轉換」,而您選取 es-ES-ElviraNeural時,文字會以西班牙文口音說出。

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

注意

默認語音是每個地區設定從 語音清單 API 傳回的第一個語音。

說話的聲音會依優先順序決定,如下所示:

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

將文字合成至語音

若要將合成語音輸出到目前的使用中輸出裝置,例如喇叭,請使用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傳遞 nullAudioConfig

注意

nullAudioConfig針對 傳遞 ,而不是省略上一個喇叭輸出範例中所做的,預設不會在目前的使用中輸出裝置上播放音訊。

將結果儲存至 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();
        });
}

自訂音訊格式

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

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

若要變更音訊格式,請使用 speechSynthesisOutputFormat 物件上的 SpeechConfig 屬性。 這個屬性需要 enum SpeechSynthesisOutputFormat類型的實例。 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 檔案。 要求大多相同,但不是使用 speakTextAsync() 函式,而是使用 speakSsmlAsync()。 此函式需要 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 的情況下變更語音,您可以使用 在 上SpeechConfigSpeechConfig.speechSynthesisVoiceName = "en-US-AvaMultilingualNeural";設定 屬性。

訂閱合成器事件

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

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

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

注意

當輸出音訊數據可供使用時,就會引發事件,這比播放輸出裝置更快。 呼叫端必須適當地同步串流和即時。

以下是示範如何訂閱事件以進行語音合成的範例。 您可以遵循快速入門中的指示,但以下列 JavaScript 程式代碼取代該SpeechSynthesis.js檔案的內容。

(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)。
  • 使用神經語音。
  • 訂閱事件並針對結果採取行動。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站上建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,請選取 [移至資源 ] 以檢視和管理密鑰。 如需 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

安裝語音 SDK 和範例

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

使用自訂端點

自定義端點的功能與用於文字到語音要求的標準端點相同。

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

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)。
  • 使用神經語音。
  • 訂閱事件並針對結果採取行動。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站上建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,請選取 [移至資源 ] 以檢視和管理密鑰。 如需 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

安裝語音 SDK 和範例

Azure-Samples/cognitive-services-speech-sdk 存放庫包含以 Swift for 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"

所有神經語音都是多語系的,並且以自己的語言和英語流暢。 例如,如果英文輸入文字是「我很高興能嘗試文字到語音轉換」,而您選取 es-ES-ElviraNeural時,文字會以西班牙文口音說出。

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

注意

默認語音是每個地區設定從 語音清單 API 傳回的第一個語音。

說話的聲音會依優先順序決定,如下所示:

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

將語音合成至檔案

建立 SpeechSynthesizer 物件。 這個物件會執行文字到語音轉換,以及輸出到說話者、檔案或其他輸出數據流。 SpeechSynthesizer 接受 做為參數:

  1. 使用 AudioOutputConfig 建構函式參數建立 實例,以自動將輸出 寫入.wav 檔案 filename

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

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

當您執行程式時,它會建立合成 的.wav 檔案,該檔案會寫入您指定的位置。 此結果是最基本的使用方式的良好範例。 接下來,您可以自定義輸出,並以記憶體內部串流處理輸出回應,以處理自定義案例。

合成至喇叭輸出

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

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

以記憶體內部數據流的形式取得結果

您可以使用產生的音訊數據作為記憶體內部數據流,而不是直接寫入檔案。 使用記憶體內部資料流,您可以建置自定義行為:

  • 將產生的位元組數位抽象化為自訂下游服務的可搜尋數據流。
  • 將結果與其他 API 或服務整合。
  • 修改音訊數據、撰寫自定義 .wav 標頭,以及執行相關工作。

您可以對上一個範例進行這項變更。 首先,請移除 AudioConfig,因為您從這個點開始手動管理輸出行為,以增加控制。 AudioConfig傳入 None 建構函式中的 SpeechSynthesizer

注意

NoneAudioConfig針對 傳遞 ,而不是省略上一個喇叭輸出範例中所做的,預設不會在目前的使用中輸出裝置上播放音訊。

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

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

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

此時,您可以使用產生的 stream 對象來實作任何自定義行為。

自訂音訊格式

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

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

若要變更音訊格式,請使用 set_speech_synthesis_output_format() 物件上的 SpeechConfig 函式。 此函式需要 enum SpeechSynthesisOutputFormat類型的實例。 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)

result = speech_synthesizer.speak_text_async("I'm excited to try text to speech").get()
stream = speechsdk.AudioDataStream(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()
    result = speech_synthesizer.speak_ssml_async(ssml_string).get()
    
    stream = speechsdk.AudioDataStream(result)
    stream.save_to_wav_file("path/to/write/file.wav")
    

注意

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

訂閱合成器事件

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

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

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

注意

當輸出音訊數據可供使用時,就會引發事件,這比播放輸出裝置更快。 呼叫端必須適當地同步串流和即時。

以下是示範如何訂閱事件以進行語音合成的範例。 您可以遵循快速入門中的指示,但以下列 Python 程式代碼取代該 speech-synthesis.py 檔案的內容:

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 使用您的自訂語音。 您可以從文字到語音轉換快速入門開始,然後使用 和speech_synthesis_voice_name更新程序代碼endpoint_id

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)。
  • 使用神經語音。
  • 訂閱事件並針對結果採取行動。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站上建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,請選取 [移至資源 ] 以檢視和管理密鑰。 如需 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

將文字轉換成語音

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

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

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

  • 標頭 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)。
  • 使用神經語音。
  • 訂閱事件並針對結果採取行動。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站上建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,請選取 [移至資源 ] 以檢視和管理密鑰。 如需 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

下載並安裝

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

  1. 執行下列 .NET CLI 命令以安裝語音 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 安裝和執行語音容器。

下一步