Speech SDK を使用して音声合成の待機時間を短縮する

アプリケーションにとって、合成の待機時間は重要です。 この記事では、待機時間を短くし、エンド ユーザーに最高のパフォーマンスを提供するためのベスト プラクティスを紹介します。

通常、次のように、first byte latencyfinish latency で待機時間を測定します。

待機時間 説明 SpeechSynthesisResult プロパティ キー
最初のバイト待機時間 合成タスクが開始されてから、オーディオ データの最初のチャンクが受信されるまでの遅延時間を示します。 SpeechServiceResponse_SynthesisFirstByteLatencyMs
完了までの待機時間 合成タスクが開始されてから、合成するオーディオ データ全体が受信されるまでの遅延時間を示します。 SpeechServiceResponse_SynthesisFinishLatencyMs

Speech SDK によって、SpeechSynthesisResult のプロパティ コレクションに待機時間が入力されます。 これらの値を次のサンプル コードに示します。

var result = await synthesizer.SpeakTextAsync(text);
Console.WriteLine($"first byte latency: \t{result.Properties.GetProperty(PropertyId.SpeechServiceResponse_SynthesisFirstByteLatencyMs)} ms");
Console.WriteLine($"finish latency: \t{result.Properties.GetProperty(PropertyId.SpeechServiceResponse_SynthesisFinishLatencyMs)} ms");
// you can also get the result id, and send to us when you need help for diagnosis
var resultId = result.ResultId;
待機時間 説明 SpeechSynthesisResult プロパティ キー
first byte latency 合成が開始されてから最初のオーディオ チャンクが受信されるまでの遅延時間を示します。 SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency 合成が開始されてから合成するオーディオ全体が受信されるまでの遅延時間を示します。 SpeechServiceResponse_SynthesisFinishLatencyMs

Speech SDK によって、待機時間が測定され、SpeechSynthesisResult のプロパティ バッグに格納されます。 それらを取得するには、次のコードを参照してください。

auto result = synthesizer->SpeakTextAsync(text).get();
auto firstByteLatency = std::stoi(result->Properties.GetProperty(PropertyId::SpeechServiceResponse_SynthesisFirstByteLatencyMs));
auto finishedLatency = std::stoi(result->Properties.GetProperty(PropertyId::SpeechServiceResponse_SynthesisFinishLatencyMs));
// you can also get the result id, and send to us when you need help for diagnosis
auto resultId = result->ResultId;
待機時間 説明 SpeechSynthesisResult プロパティ キー
first byte latency 合成が開始されてから最初のオーディオ チャンクが受信されるまでの遅延時間を示します。 SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency 合成が開始されてから合成するオーディオ全体が受信されるまでの遅延時間を示します。 SpeechServiceResponse_SynthesisFinishLatencyMs

Speech SDK によって、待機時間が測定され、SpeechSynthesisResult のプロパティ バッグに格納されます。 それらを取得するには、次のコードを参照してください。

SpeechSynthesisResult result = synthesizer.SpeakTextAsync(text).get();
System.out.println("first byte latency: \t" + result.getProperties().getProperty(PropertyId.SpeechServiceResponse_SynthesisFirstByteLatencyMs) + " ms.");
System.out.println("finish latency: \t" + result.getProperties().getProperty(PropertyId.SpeechServiceResponse_SynthesisFinishLatencyMs) + " ms.");
// you can also get the result id, and send to us when you need help for diagnosis
String resultId = result.getResultId();
待機時間 説明 SpeechSynthesisResult プロパティ キー
first byte latency 合成が開始されてから最初のオーディオ チャンクが受信されるまでの遅延時間を示します。 SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency 合成が開始されてから合成するオーディオ全体が受信されるまでの遅延時間を示します。 SpeechServiceResponse_SynthesisFinishLatencyMs

Speech SDK によって、待機時間が測定され、SpeechSynthesisResult のプロパティ バッグに格納されます。 それらを取得するには、次のコードを参照してください。

result = synthesizer.speak_text_async(text).get()
first_byte_latency = int(result.properties.get_property(speechsdk.PropertyId.SpeechServiceResponse_SynthesisFirstByteLatencyMs))
finished_latency = int(result.properties.get_property(speechsdk.PropertyId.SpeechServiceResponse_SynthesisFinishLatencyMs))
# you can also get the result id, and send to us when you need help for diagnosis
result_id = result.result_id
待機時間 説明 SPXSpeechSynthesisResult プロパティ キー
first byte latency 合成が開始されてから最初のオーディオ チャンクが受信されるまでの遅延時間を示します。 SPXSpeechServiceResponseSynthesisFirstByteLatencyMs
finish latency 合成が開始されてから合成するオーディオ全体が受信されるまでの遅延時間を示します。 SPXSpeechServiceResponseSynthesisFinishLatencyMs

Speech SDK によって、待機時間が測定され、SPXSpeechSynthesisResult のプロパティ バッグに格納されます。 それらを取得するには、次のコードを参照してください。

SPXSpeechSynthesisResult *speechResult = [speechSynthesizer speakText:text];
int firstByteLatency = [intString [speechResult.properties getPropertyById:SPXSpeechServiceResponseSynthesisFirstByteLatencyMs]];
int finishedLatency = [intString [speechResult.properties getPropertyById:SPXSpeechServiceResponseSynthesisFinishLatencyMs]];
// you can also get the result id, and send to us when you need help for diagnosis
NSString *resultId = result.resultId;

ほとんどの場合、最初のバイトまでの待機時間は、完了までの待機時間よりも短くなります。 最初のバイト待機時間はテキストの長さとは無関係ですが、完了までの待機時間はテキストの長さによって増減します。

ユーザーが経験する待機時間 (ユーザーがサウンドを聞くまでの待機時間) を最小限に抑える、つまり音声合成サービスの 1 つのネットワーク ルート トリップ時間に最初のオーディオ チャンク待機時間をプラスした時間に抑えることができれば理想的です。

ストリーミング

待機時間を短縮するうえで、ストリーミングが重要な鍵を握っています。 クライアント コードでは、最初のオーディオ チャンクを受信したときに再生を開始できます。 サービス シナリオでは、オーディオ全体を待機するのではなく、オーディオ チャンクをすぐにクライアントに転送できます。

Speech SDK の PullAudioOutputStreamPushAudioOutputStreamSynthesizing イベント、および AudioDataStream を使用して、ストリーミングを有効にすることができます。

例として、AudioDataStream を取り上げます。

using (var synthesizer = new SpeechSynthesizer(config, null as AudioConfig))
{
    using (var result = await synthesizer.StartSpeakingTextAsync(text))
    {
        using (var audioDataStream = AudioDataStream.FromResult(result))
        {
            byte[] buffer = new byte[16000];
            uint filledSize = 0;
            while ((filledSize = audioDataStream.ReadData(buffer)) > 0)
            {
                Console.WriteLine($"{filledSize} bytes received.");
            }
        }
    }
}

Speech SDK の PullAudioOutputStreamPushAudioOutputStreamSynthesizing イベントAudioDataStream を使用して、ストリーミングを有効にすることができます。

例として、AudioDataStream を取り上げます。

auto synthesizer = SpeechSynthesizer::FromConfig(config, nullptr);
auto result = synthesizer->SpeakTextAsync(text).get();
auto audioDataStream = AudioDataStream::FromResult(result);
uint8_t buffer[16000];
uint32_t filledSize = 0;
while ((filledSize = audioDataStream->ReadData(buffer, sizeof(buffer))) > 0)
{
    cout << filledSize << " bytes received." << endl;
}

Speech SDK の PullAudioOutputStreamPushAudioOutputStreamSynthesizing イベントAudioDataStream を使用して、ストリーミングを有効にすることができます。

例として、AudioDataStream を取り上げます。

SpeechSynthesizer synthesizer = new SpeechSynthesizer(config, null);
SpeechSynthesisResult result = synthesizer.StartSpeakingTextAsync(text).get();
AudioDataStream audioDataStream = AudioDataStream.fromResult(result);
byte[] buffer = new byte[16000];
long filledSize = audioDataStream.readData(buffer);
while (filledSize > 0) {
    System.out.println(filledSize + " bytes received.");
    filledSize = audioDataStream.readData(buffer);
}

Speech SDK の PullAudioOutputStreamPushAudioOutputStreamSynthesizing イベントAudioDataStream を使用して、ストリーミングを有効にすることができます。

例として、AudioDataStream を取り上げます。

speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=None)
result = speech_synthesizer.start_speaking_text_async(text).get()
audio_data_stream = speechsdk.AudioDataStream(result)
audio_buffer = bytes(16000)
filled_size = audio_data_stream.read_data(audio_buffer)
while filled_size > 0:
    print("{} bytes received.".format(filled_size))
    filled_size = audio_data_stream.read_data(audio_buffer)

Speech SDK の SPXPullAudioOutputStreamSPXPushAudioOutputStreamSynthesizing イベントSPXAudioDataStream を使用して、ストリーミングを有効にすることができます。

例として、AudioDataStream を取り上げます。

SPXSpeechSynthesizer *synthesizer = [[SPXSpeechSynthesizer alloc] initWithSpeechConfiguration:speechConfig audioConfiguration:nil];
SPXSpeechSynthesisResult *speechResult = [synthesizer startSpeakingText:inputText];
SPXAudioDataStream *stream = [[SPXAudioDataStream alloc] initFromSynthesisResult:speechResult];
NSMutableData* data = [[NSMutableData alloc]initWithCapacity:16000];
while ([stream readData:data length:16000] > 0) {
    // Read data here
}

SpeechSynthesizer の事前接続と再利用

Speech SDK は WebSocket を使用してサービスと通信します。 ネットワーク待機時間は 1 つのルート トリップ時間 (RTT) に抑えることが理想的です。 接続が新たに確立される場合、ネットワーク待機時間には、接続を確立するための時間が余分に加わります。 WebSocket 接続を確立するには、TCP ハンドシェイク、SSL ハンドシェイク、HTTP 接続、プロトコルのアップグレードが必要で、これにより遅延が生じます。 接続の待機時間を回避するために、SpeechSynthesizer の事前接続と再利用をお勧めします。

接続前

事前接続するために、接続がもうすぐ必要になると思われる時点で、音声サービスへの接続を確立します。 たとえば、クライアントで音声ボットを構築している場合は、ユーザーが話し始めたタイミングで音声合成サービスに事前接続し、ボットの応答テキストの準備ができたタイミングで SpeakTextAsync を呼び出すことができます。

using (var synthesizer = new SpeechSynthesizer(uspConfig, null as AudioConfig))
{
    using (var connection = Connection.FromSpeechSynthesizer(synthesizer))
    {
        connection.Open(true);
    }
    await synthesizer.SpeakTextAsync(text);
}
auto synthesizer = SpeechSynthesizer::FromConfig(config, nullptr);
auto connection = Connection::FromSpeechSynthesizer(synthesizer);
connection->Open(true);
SpeechSynthesizer synthesizer = new SpeechSynthesizer(speechConfig, (AudioConfig) null);
Connection connection = Connection.fromSpeechSynthesizer(synthesizer);
connection.openConnection(true);
synthesizer = speechsdk.SpeechSynthesizer(config, None)
connection = speechsdk.Connection.from_speech_synthesizer(synthesizer)
connection.open(True)
SPXSpeechSynthesizer* synthesizer = [[SPXSpeechSynthesizer alloc]initWithSpeechConfiguration:self.speechConfig audioConfiguration:nil];
SPXConnection* connection = [[SPXConnection alloc]initFromSpeechSynthesizer:synthesizer];
[connection open:true];

Note

合成テキストが使用可能な場合は、SpeakTextAsync を呼び出してオーディオを合成します。 SDK は接続を処理します。

SpeechSynthesizer の再利用

接続の待機時間を短縮するもう 1 つの方法は、SpeechSynthesizer を再利用することです。そうすることで、各合成に新しい SpeechSynthesizer を作成する必要がなくなります。 サービス シナリオでは、オブジェクト プールを使用することをお勧めします。C#Java のサンプル コードを参照してください。

圧縮したオーディオをネットワーク経由で送信する

ネットワークが不安定であったり、帯域幅が限られている場合は、ペイロードのサイズも待機時間に影響します。 一方、圧縮オーディオ形式は、ユーザーのネットワーク帯域幅を節約するのに役立ちます。これは、モバイル ユーザーにとって特に有益です。

opuswebmmp3silk などさまざまな圧縮形式がサポートされています。SpeechSynthesisOutputFormat の完全なリストを参照してください。 たとえば、Riff24Khz16BitMonoPcm 形式のビットレートは 384 kbps ですが、Audio24Khz48KBitRateMonoMp3 では 48 kbps です。 pcm 出力形式が設定されている場合、Speech SDK では転送に圧縮形式が自動的に使用されます。 Linux および Windows の場合、この機能を有効にするには GStreamer が必要です。 Speech SDK を使用するために GStreamer をインストールして構成するには、こちらの手順を参照してください。 Android、iOS、macOS の場合、バージョン 1.20 以降では追加の構成は必要ありません。

その他のヒント

CRL ファイルをキャッシュする

Speech SDK では、証明書を確認するために CRL ファイルが使用されます。 期限切れになるまで、CRL ファイルをキャッシュすることで、CRL ファイルを毎回ダウンロードせずに済みます。 詳細については、Linux 用に OpenSSL を構成する方法に関するページを参照してください。

最新の Speech SDK を使用する

Speech SDK のパフォーマンスが向上し続けているため、アプリケーションで最新の Speech SDK を使用してください。

ロード テストのガイドライン

ロード テストを使用して、音声合成サービスの容量と待機時間をテストすることができます。 ガイドラインを次に示します。

  • 音声合成サービスには自動スケーリング機能がありますが、スケールアウトには時間がかかります。コンカレンシーが短時間で増加すると、クライアントでは、待機時間が増大したり、429 エラー コード (要求が多すぎる) が取得される可能性があります。 そのため、ロード テストでは、同時実行を徐々に増やすことをお勧めします。 詳細については、こちらの記事を参照してください。特に、ワークロード パターンのこの例をご確認ください。
  • ロード テストや待機時間数の取得には、オブジェクト プール (C# および Java) を使用するサンプルを利用できます。 サンプルのテストのターン数と同時実行を、目的の同時実行に合わせて変更できます。
  • このサービスには実際のトラフィックに基づくクォータ制限があるため、実際のトラフィックよりも高いコンカレンシーでロード テストを実行したい場合は、テストの前に接続しておきます。

次のステップ