如何從文字合成語音
參考文件 | 套件 (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 傳回的第一個語音。
說話的聲音會依優先順序決定,如下所示:
- 如果您未設定
SpeechSynthesisVoiceName
或SpeechSynthesisLanguage
,則表示en-US
的預設語音。 - 如果您只設定
SpeechSynthesisLanguage
,則指定地區設定的預設語音會說話。 - 如果 同時
SpeechSynthesisVoiceName
設定 和SpeechSynthesisLanguage
,則會SpeechSynthesisLanguage
忽略設定。 您使用SpeechSynthesisVoiceName
語音指定的聲音。 - 如果使用語音合成標記語言 (SSML) 設定語音專案,
SpeechSynthesisVoiceName
則會忽略 和SpeechSynthesisLanguage
設定。
將語音合成至檔案
建立 SpeechSynthesizer 物件。 下列代碼段所示的這個物件會執行文字到語音轉換,以及輸出到說話者、檔案或其他輸出數據流。 SpeechSynthesizer
接受 做為參數:
- 您在上一個步驟中建立的SpeechConfig物件。
- AudioConfig 物件,指定應該如何處理輸出結果。
建立
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 資源,並導致對象在處置之後超出範圍。使用另一個
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
區塊,因為您從這個點開始手動管理輸出行為,以增加控制。 AudioConfig
傳入 null
建構函式中的 SpeechSynthesizer
。
注意
針對 null
AudioConfig
傳遞 ,而不是省略在先前的喇叭輸出範例中,預設不會在目前的使用中輸出裝置上播放音訊。
將結果儲存至 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()
式。 此函式需要 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 進行自定義,您可以進行稍微變更以切換語音。
為根項目目錄中的 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
參數來變更語音。 如需支援神經語音的完整清單,請參閱 支持的語言。變更語音合成要求以參考您的 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 的情況下變更語音,您可以使用 在 上SpeechConfig
SpeechConfig.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 傳回的第一個語音。
說話的聲音會依優先順序決定,如下所示:
- 如果您未設定
SpeechSynthesisVoiceName
或SpeechSynthesisLanguage
,則表示en-US
的預設語音。 - 如果您只設定
SpeechSynthesisLanguage
,則指定地區設定的預設語音會說話。 - 如果 同時
SpeechSynthesisVoiceName
設定 和SpeechSynthesisLanguage
,則會SpeechSynthesisLanguage
忽略設定。 您使用SpeechSynthesisVoiceName
語音指定的聲音。 - 如果使用語音合成標記語言 (SSML) 設定語音專案,
SpeechSynthesisVoiceName
則會忽略 和SpeechSynthesisLanguage
設定。
將語音合成至檔案
建立 SpeechSynthesizer 物件。 下列代碼段所示的這個物件會執行文字到語音轉換,以及輸出到說話者、檔案或其他輸出數據流。 SpeechSynthesizer
接受 做為參數:
- 您在上一個步驟中建立的SpeechConfig物件。
- AudioConfig 物件,指定應該如何處理輸出結果。
建立
AudioConfig
實體,以使用FromWavFileOutput()
函式自動將輸出寫入.wav檔案:void synthesizeSpeech() { auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion"); auto audioConfig = AudioConfig::FromWavFileOutput("path/to/write/file.wav"); }
具現化
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
區塊,因為您從這個點開始手動管理輸出行為,以增加控制。 AudioConfig
傳入 NULL
建構函式中的 SpeechSynthesizer
。
注意
針對 NULL
AudioConfig
傳遞 ,而不是省略在先前的喇叭輸出範例中,預設不會在目前的使用中輸出裝置上播放音訊。
將結果儲存至 SpeechSynthesisResult 變數。 getter 會GetAudioData
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
對象來實作任何自定義行為。
自訂音訊格式
您可以自訂音訊輸出屬性,包括:
- 音訊檔類型
- 採樣速率
- 位元深度
若要變更音訊格式,請使用 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 進行自定義,請進行稍微變更以切換語音。
為根項目目錄中的 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
參數來變更語音。 如需支援神經語音的完整清單,請參閱 支持的語言。變更語音合成要求以參考您的 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
SpeechConfig.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)。
- 使用神經語音。
- 訂閱事件並針對結果採取行動。
必要條件
安裝語音 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
如需類別的詳細資訊,請參閱 SpeechConfig
和 SpeechSynthesizer
參考檔。
文字轉換語音至記憶體內資料流
您可以使用產生的音訊數據作為記憶體內部數據流,而不是直接寫入檔案。 使用記憶體內部資料流,您可以建置自定義行為:
- 將產生的位元組數位抽象化為自訂下游服務的可搜尋數據流。
- 將結果與其他 API 或服務整合。
- 修改音訊數據、撰寫自定義.wav標頭,以及執行相關工作。
您可以對上一個範例進行這項變更。 AudioConfig
拿掉 區塊,因為您從這個點開始手動管理輸出行為,以增加控制。 然後在建構函式中SpeechSynthesizer
傳遞 nil
AudioConfig
。
注意
nil
AudioConfig
針對 傳遞 ,而不是省略上一個喇叭輸出範例中所做的,預設不會在目前的使用中輸出裝置上播放音訊。
將結果儲存至 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
如需類別的詳細資訊,請參閱 SpeechConfig
和 SpeechSynthesizer
參考檔。
選取合成語言和語音
語音服務中的文字轉換語音功能支援 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 傳回的第一個語音。
說話的聲音會依優先順序決定,如下所示:
- 如果您未設定
SpeechSynthesisVoiceName
或SpeechSynthesisLanguage
,則表示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 的情況下設定語音,您可以使用 來設定 上的 SpeechConfig
speechConfig.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 安裝和執行語音容器。
在此操作指南中,您將了解進行文字轉換語音合成的常用設計模式。
如需下列區域的詳細資訊,請參閱 什麼是文字到語音轉換?
- 以記憶體內部數據流的形式取得回應。
- 自訂輸出取樣率和比特率。
- 使用語音合成標記語言提交合成要求(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 傳回的第一個語音。
說話的聲音會依優先順序決定,如下所示:
- 如果您未設定
SpeechSynthesisVoiceName
或SpeechSynthesisLanguage
,則表示en-US
的預設語音。 - 如果您只設定
SpeechSynthesisLanguage
,則指定地區設定的預設語音會說話。 - 如果 同時
SpeechSynthesisVoiceName
設定 和SpeechSynthesisLanguage
,則會SpeechSynthesisLanguage
忽略設定。 您使用SpeechSynthesisVoiceName
說話所指定的語音。 - 如果使用語音合成標記語言 (SSML) 設定語音專案,
SpeechSynthesisVoiceName
則會忽略 和SpeechSynthesisLanguage
設定。
將語音合成至檔案
建立 SpeechSynthesizer
物件。 這個物件會執行文字到語音轉換,以及輸出到說話者、檔案或其他輸出數據流。 SpeechSynthesizer
接受 做為參數:
SpeechConfig
您在上一個步驟中建立的物件。AudioConfig
物件,指定應該如何處理輸出結果。
建立
AudioConfig
實體,以使用fromWavFileOutput()
靜態函式自動將輸出寫入.wav檔案:public static void main(String[] args) { SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion"); AudioConfig audioConfig = AudioConfig.fromWavFileOutput("path/to/write/file.wav"); }
具現化
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
傳遞 null
AudioConfig
。
注意
null
AudioConfig
針對 傳遞 ,而不是省略上一個喇叭輸出範例中所做的,預設不會在目前的使用中輸出裝置上播放音訊。
將結果儲存至 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()
式。 此函式需要 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 進行自定義,您可以進行稍微變更以切換語音。
為根項目目錄中的 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
參數來變更語音。 如需支援神經語音的完整清單,請參閱 支持的語言。變更語音合成要求以參考您的 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 傳回的第一個語音。
說話的聲音會依優先順序決定,如下所示:
- 如果您未設定
SpeechSynthesisVoiceName
或SpeechSynthesisLanguage
,則表示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
傳遞 null
AudioConfig
。
注意
null
AudioConfig
針對 傳遞 ,而不是省略上一個喇叭輸出範例中所做的,預設不會在目前的使用中輸出裝置上播放音訊。
將結果儲存至 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 進行自定義,您可以進行稍微變更以切換語音。
為根項目目錄中的 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
參數來變更語音。 如需支援神經語音的完整清單,請參閱 支持的語言。變更語音合成要求以參考您的 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 的情況下變更語音,您可以使用 在 上SpeechConfig
SpeechConfig.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)。
- 使用神經語音。
- 訂閱事件並針對結果採取行動。
必要條件
安裝語音 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)。
- 使用神經語音。
- 訂閱事件並針對結果採取行動。
必要條件
安裝語音 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 傳回的第一個語音。
說話的聲音會依優先順序決定,如下所示:
- 如果您未設定
SpeechSynthesisVoiceName
或SpeechSynthesisLanguage
,則表示en-US
的預設語音。 - 如果您只設定
SpeechSynthesisLanguage
,則指定地區設定的預設語音會說話。 - 如果 同時
SpeechSynthesisVoiceName
設定 和SpeechSynthesisLanguage
,則會SpeechSynthesisLanguage
忽略設定。 您使用SpeechSynthesisVoiceName
語音指定的聲音。 - 如果使用語音合成標記語言 (SSML) 設定語音專案,
SpeechSynthesisVoiceName
則會忽略 和SpeechSynthesisLanguage
設定。
將語音合成至檔案
建立 SpeechSynthesizer 物件。 這個物件會執行文字到語音轉換,以及輸出到說話者、檔案或其他輸出數據流。 SpeechSynthesizer
接受 做為參數:
SpeechConfig
您在上一個步驟中建立的物件。AudioOutputConfig
物件,指定應該如何處理輸出結果。
使用
AudioOutputConfig
建構函式參數建立 實例,以自動將輸出 寫入.wav 檔案filename
:audio_config = speechsdk.audio.AudioOutputConfig(filename="path/to/write/file.wav")
藉由將對象和
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
。
注意
None
AudioConfig
針對 傳遞 ,而不是省略上一個喇叭輸出範例中所做的,預設不會在目前的使用中輸出裝置上播放音訊。
將結果儲存至 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 進行自定義,請進行稍微變更以切換語音。
為根項目目錄中的 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
參數來變更語音。 如需支援神經語音的完整清單,請參閱 支持的語言。變更語音合成要求以參考您的 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 的情況下變更語音,您可以使用 在 上SpeechConfig
speech_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)。
- 使用神經語音。
- 訂閱事件並針對結果採取行動。
必要條件
將文字轉換成語音
在命令提示字元中,執行下列命令。 將這些值插入命令中:
- 您的語音資源金鑰
- 您的語音資源區域
您可能也想要變更下列值:
- 標頭
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 快速入門 ,以了解平臺的其他需求。
執行下列 .NET CLI 命令以安裝語音 CLI:
dotnet tool install --global Microsoft.CognitiveServices.Speech.CLI
執行下列命令來設定您的語音資源金鑰和區域。 以您的語音資源金鑰取代
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 安裝和執行語音容器。