Udostępnij za pośrednictwem


Mniejsze opóźnienia syntezy mowy podczas korzystania z zestawu SDK usługi Mowa

W tym artykule przedstawimy najlepsze rozwiązania, aby zmniejszyć opóźnienie syntezy mowy w tekście i zapewnić użytkownikom końcowym najlepszą wydajność.

Zwykle mierzymy opóźnienie według first byte latency wartości i finish latency, w następujący sposób:

Opóźnienie opis SpeechSynthesisResult , klucz właściwości
pierwsze opóźnienie bajtów Wskazuje opóźnienie czasu między rozpoczęciem zadania syntezy a otrzymaniem pierwszego fragmentu danych dźwiękowych. SpeechServiceResponse_SynthesisFirstByteLatencyMs
opóźnienie zakończenia Wskazuje opóźnienie czasu między rozpoczęciem zadania syntezy a otrzymaniem wszystkich syntetyzowanych danych dźwiękowych. SpeechServiceResponse_SynthesisFinishLatencyMs

Zestaw SPEECH SDK umieszcza czasy trwania opóźnienia w kolekcji SpeechSynthesisResultWłaściwości elementu . Poniższy przykładowy kod przedstawia te wartości.

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;
Opóźnienie opis SpeechSynthesisResult , klucz właściwości
first byte latency Wskazuje opóźnienie czasu między rozpoczęciem syntezy a pierwszym fragmentem dźwięku. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Wskazuje opóźnienie czasu między rozpoczęciem syntezy a całym syntetyzowanym dźwiękiem. SpeechServiceResponse_SynthesisFinishLatencyMs

Zestaw SPEECH SDK zmierzył opóźnienia i umieścił je w torbie właściwości .SpeechSynthesisResult Zapoznaj się z poniższymi kodami, aby je pobrać.

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;
Opóźnienie opis SpeechSynthesisResult , klucz właściwości
first byte latency Wskazuje opóźnienie czasu między rozpoczęciem syntezy a pierwszym fragmentem dźwięku. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Wskazuje opóźnienie czasu między rozpoczęciem syntezy a całym syntetyzowanym dźwiękiem. SpeechServiceResponse_SynthesisFinishLatencyMs

Zestaw SPEECH SDK zmierzył opóźnienia i umieścił je w torbie właściwości .SpeechSynthesisResult Zapoznaj się z poniższymi kodami, aby je pobrać.

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();
Opóźnienie opis SpeechSynthesisResult , klucz właściwości
first byte latency Wskazuje opóźnienie czasu między rozpoczęciem syntezy a pierwszym fragmentem dźwięku. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Wskazuje opóźnienie czasu między rozpoczęciem syntezy a całym syntetyzowanym dźwiękiem. SpeechServiceResponse_SynthesisFinishLatencyMs

Zestaw SPEECH SDK zmierzył opóźnienia i umieścił je w torbie właściwości .SpeechSynthesisResult Zapoznaj się z poniższymi kodami, aby je pobrać.

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
Opóźnienie opis SPXSpeechSynthesisResult , klucz właściwości
first byte latency Wskazuje opóźnienie czasu między rozpoczęciem syntezy a pierwszym fragmentem dźwięku. SPXSpeechServiceResponseSynthesisFirstByteLatencyMs
finish latency Wskazuje opóźnienie czasu między rozpoczęciem syntezy a całym syntetyzowanym dźwiękiem. SPXSpeechServiceResponseSynthesisFinishLatencyMs

Zestaw SPEECH SDK zmierzył opóźnienia i umieścił je w torbie właściwości .SPXSpeechSynthesisResult Zapoznaj się z poniższymi kodami, aby je pobrać.

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;

Pierwsze opóźnienie bajtów jest mniejsze niż opóźnienie zakończenia w większości przypadków. Pierwsze opóźnienie bajtów jest niezależne od długości tekstu, a opóźnienie zakończenia zwiększa się wraz z długością tekstu.

W idealnym przypadku chcemy zminimalizować opóźnienie użytkownika (opóźnienie, zanim użytkownik usłyszy dźwięk) do jednego czasu podróży trasy sieciowej oraz pierwsze opóźnienie fragmentu dźwięku usługi syntezy mowy.

Przesyłanie strumieniowe

Przesyłanie strumieniowe ma kluczowe znaczenie dla zmniejszenia opóźnienia. Kod klienta może rozpocząć odtwarzanie po odebraniu pierwszego fragmentu dźwięku. W scenariuszu usługi można natychmiast przekazać fragmenty audio do klientów, zamiast czekać na cały dźwięk.

Do włączenia przesyłania strumieniowego można użyć zestawu PullAudioOutputStreamSDK rozpoznawania mowy, PushAudioOutputStreamSynthesizing zdarzenia i AudioDataStream zestawu SDK usługi Mowa.

Przykład 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.");
            }
        }
    }
}

Do włączenia przesyłania strumieniowego można użyć elementu PullAudioOutputStream, PushAudioOutputStream, Synthesizing zdarzenia i AudioDataStream zestawu SDK usługi Mowa.

Przykład 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;
}

Do włączenia przesyłania strumieniowego można użyć elementu PullAudioOutputStream, PushAudioOutputStream, Synthesizing zdarzenia i AudioDataStream zestawu SDK usługi Mowa.

Przykład 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);
}

Do włączenia przesyłania strumieniowego można użyć elementu PullAudioOutputStream, PushAudioOutputStream, Synthesizing zdarzenia i AudioDataStream zestawu SDK usługi Mowa.

Przykład 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)

Do włączenia przesyłania strumieniowego można użyć elementu SPXPullAudioOutputStream, SPXPushAudioOutputStream, Synthesizing zdarzenia i SPXAudioDataStream zestawu SDK usługi Mowa.

Przykład 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
}

Wstępne łączenie i ponowne używanie funkcji SpeechSynthesizer

Zestaw SPEECH SDK używa protokołu websocket do komunikowania się z usługą. W idealnym przypadku opóźnienie sieci powinno być jednym czasem podróży trasą (RTT). Jeśli połączenie jest nowo ustanowione, opóźnienie sieci obejmuje dodatkowy czas na nawiązanie połączenia. Utworzenie połączenia protokołu websocket wymaga uzgadniania PROTOKOŁU TCP, uzgadniania SSL, połączenia HTTP i uaktualniania protokołu, co wprowadza opóźnienie czasu. Aby uniknąć opóźnienia połączenia, zalecamy wstępne łączenie i ponowne używanie elementu SpeechSynthesizer.

Wstępne łączenie

Aby wstępnie nawiązać połączenie, ustanów połączenie z usługą Rozpoznawanie mowy, gdy wiesz, że połączenie jest potrzebne wkrótce. Jeśli na przykład tworzysz bota mowy w kliencie, możesz wstępnie nawiązać połączenie z usługą syntezy mowy, gdy użytkownik zacznie rozmawiać, i wywoływać SpeakTextAsync , gdy tekst odpowiedzi bota jest gotowy.

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];

Uwaga

Jeśli tekst jest dostępny, po prostu wywołaj SpeakTextAsync metodę syntezowania dźwięku. Zestaw SDK będzie obsługiwać połączenie.

Ponowne używanie funkcji SpeechSynthesizer

Innym sposobem zmniejszenia opóźnienia połączenia jest ponowne użycie SpeechSynthesizer elementu , dzięki czemu nie trzeba tworzyć nowych SpeechSynthesizer dla każdej syntezy. Zalecamy używanie puli obiektów w scenariuszu usługi. Zobacz nasz przykładowy kod dla języków C# i Java.

Przesyłanie skompresowanego dźwięku za pośrednictwem sieci

Gdy sieć jest niestabilna lub z ograniczoną przepustowością, rozmiar ładunku również wpływa na opóźnienie. Tymczasem skompresowany format audio pomaga zaoszczędzić przepustowość sieci użytkowników, co jest szczególnie cenne dla użytkowników mobilnych.

Obsługujemy wiele skompresowanych formatów, w tym opus, webm, mp3, silki tak dalej, zobacz pełną listę w temacie SpeechSynthesisOutputFormat. Na przykład szybkość transmisji bitów Riff24Khz16BitMonoPcm w formacie wynosi 384 kb/s, a Audio24Khz48KBitRateMonoMp3 koszt wynosi tylko 48 kb/s. Zestaw SPEECH SDK automatycznie używa skompresowanego formatu do transmisji po ustawieniu formatu wyjściowego pcm . W przypadku systemów Linux i Windows GStreamer należy włączyć tę funkcję. Zapoznaj się z tą instrukcją , aby zainstalować i skonfigurować GStreamer zestaw SPEECH SDK. W przypadku systemów Android, iOS i macOS nie jest wymagana dodatkowa konfiguracja, począwszy od wersji 1.20.

Przesyłanie strumieniowe tekstu wejściowego

Przesyłanie strumieniowe tekstu umożliwia przetwarzanie tekstu w czasie rzeczywistym na potrzeby szybkiego generowania dźwięku. Doskonale nadaje się do dynamicznej wokalizacji tekstu, takiej jak odczytywanie danych wyjściowych z modeli sztucznej inteligencji, takich jak GPT w czasie rzeczywistym. Ta funkcja minimalizuje opóźnienia i poprawia płynność i czas odpowiedzi danych wyjściowych dźwięku, dzięki czemu idealnie nadaje się do interaktywnych aplikacji, wydarzeń na żywo i dynamicznych dialogów opartych na sztucznej inteligencji.

Jak używać przesyłania strumieniowego tekstu

Przesyłanie strumieniowe tekstu jest obsługiwane w językach C#, C++ i Python z zestawem Speech SDK.

Aby użyć funkcji przesyłania strumieniowego tekstu, połącz się z punktem końcowym protokołu websocket w wersji 2: wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

Zobacz przykładowy kod ustawiania punktu końcowego:

// IMPORTANT: MUST use the websocket v2 endpoint
var ttsEndpoint = $"wss://{Environment.GetEnvironmentVariable("AZURE_TTS_REGION")}.tts.speech.microsoft.com/cognitiveservices/websocket/v2";
var speechConfig = SpeechConfig.FromEndpoint(
    new Uri(ttsEndpoint),
    Environment.GetEnvironmentVariable("AZURE_TTS_API_KEY"));

Kluczowe kroki

  1. Utwórz żądanie strumienia tekstu: użyj polecenia SpeechSynthesisRequestInputType.TextStream , aby zainicjować strumień tekstowy.

  2. Ustaw właściwości globalne: Dostosuj ustawienia, takie jak format danych wyjściowych i nazwa głosu bezpośrednio, ponieważ funkcja obsługuje częściowe wprowadzanie tekstu i nie obsługuje języka SSML. Zapoznaj się z poniższym przykładowym kodem, aby uzyskać instrukcje dotyczące sposobu ich ustawiania. Funkcja przesyłania strumieniowego tekstu openAI na głosy mowy nie jest obsługiwana przez funkcję przesyłania strumieniowego tekstu. Zobacz tę tabelę języków, aby uzyskać pełną obsługę języka.

    // Set output format
    speechConfig.SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm);
    
    // Set a voice name
    SpeechConfig.SetProperty(PropertyId.SpeechServiceConnection_SynthVoice, "en-US-AvaMultilingualNeural");
    
  3. Przesyłanie strumieniowe tekstu: dla każdego fragmentu tekstu wygenerowanego na podstawie modelu GPT użyj polecenia request.InputStream.Write(text); , aby wysłać tekst do strumienia.

  4. Zamknij strumień: gdy model GPT zakończy swoje dane wyjściowe, zamknij strumień przy użyciu polecenia request.InputStream.Close();.

Aby uzyskać szczegółową implementację , zobacz przykładowy kod w witrynie GitHub

Aby użyć funkcji przesyłania strumieniowego tekstu, połącz się z punktem końcowym protokołu websocket w wersji 2: wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

Zobacz przykładowy kod ustawiania punktu końcowego:

# IMPORTANT: MUST use the websocket v2 endpoint
speech_config = speechsdk.SpeechConfig(endpoint=f"wss://{os.getenv('AZURE_TTS_REGION')}.tts.speech.microsoft.com/cognitiveservices/websocket/v2",
                                       subscription=os.getenv("AZURE_TTS_API_KEY"))

Kluczowe kroki

  1. Utwórz żądanie strumienia tekstu: użyj polecenia speechsdk.SpeechSynthesisRequestInputType.TextStream , aby zainicjować strumień tekstowy.

  2. Ustaw właściwości globalne: Dostosuj ustawienia, takie jak format danych wyjściowych i nazwa głosu bezpośrednio, ponieważ funkcja obsługuje częściowe wprowadzanie tekstu i nie obsługuje języka SSML. Zapoznaj się z poniższym przykładowym kodem, aby uzyskać instrukcje dotyczące sposobu ich ustawiania. Funkcja przesyłania strumieniowego tekstu openAI na głosy mowy nie jest obsługiwana przez funkcję przesyłania strumieniowego tekstu. Zobacz tę tabelę języków, aby uzyskać pełną obsługę języka.

    # set a voice name
    speech_config.speech_synthesis_voice_name = "en-US-AvaMultilingualNeural"
    
  3. Przesyłanie strumieniowe tekstu: dla każdego fragmentu tekstu wygenerowanego na podstawie modelu GPT użyj polecenia request.input_stream.write(text) , aby wysłać tekst do strumienia.

  4. Zamknij strumień: gdy model GPT zakończy swoje dane wyjściowe, zamknij strumień przy użyciu polecenia request.input_stream.close().

Aby uzyskać szczegółową implementację, zobacz przykładowy kod w witrynie GitHub.

Przykładowy kod języka C++ nie jest teraz dostępny. Przykładowy kod pokazujący, jak używać przesyłania strumieniowego tekstu, zobacz:

Przykładowy kod pokazujący, jak używać przesyłania strumieniowego tekstu, zobacz:

Przykładowy kod pokazujący, jak używać przesyłania strumieniowego tekstu, zobacz:

Inne porady

Buforowanie plików listy CRL

Zestaw SPEECH SDK używa plików listy CRL do sprawdzania certyfikacji. Buforowanie plików listy CRL do czasu wygaśnięcia pomaga uniknąć pobierania plików listy CRL za każdym razem. Aby uzyskać szczegółowe informacje, zobacz Jak skonfigurować protokół OpenSSL dla systemu Linux.

Korzystanie z najnowszego zestawu SPEECH SDK

Stale poprawiamy wydajność zestawu SDK usługi Mowa, więc staraj się używać najnowszego zestawu SDK usługi Mowa w aplikacji.

Wytyczne dotyczące testu obciążeniowego

Możesz użyć testu obciążeniowego, aby przetestować pojemność i opóźnienie usługi syntezy mowy. Oto kilka wskazówek:

  • Usługa syntezy mowy ma możliwość automatycznego skalowania, ale wymaga czasu na skalowanie w poziomie. Jeśli współbieżność zostanie zwiększona w krótkim czasie, klient może uzyskać długie opóźnienie lub 429 kod błędu (zbyt wiele żądań). Dlatego zalecamy zwiększenie współbieżności krok po kroku w teście obciążeniowym. Zobacz ten artykuł , aby uzyskać więcej szczegółów, zwłaszcza w tym przykładzie wzorców obciążeń.
  • Możesz użyć naszego przykładu przy użyciu puli obiektów (C# i Java) do testowania obciążenia i pobierania liczb opóźnień. Możesz zmodyfikować zmiany w testach i współbieżność w przykładzie, aby spełnić wymagania współbieżności docelowej.
  • Usługa ma ograniczenie limitu przydziału na podstawie ruchu rzeczywistego, dlatego jeśli chcesz przeprowadzić test obciążeniowy z współbieżnością większą niż rzeczywisty ruch, połącz się przed testem.

Następne kroki