Ridurre la latenza della sintesi vocale con Speech SDK

In questo articolo verranno illustrate le procedure consigliate per ridurre la latenza della sintesi vocale e offrire prestazioni ottimali agli utenti finali.

In genere, la latenza viene misurata in base a first byte latency e finish latency, come indicato di seguito:

Latenza Descrizione Chiave della proprietà SpeechSynthesisResult
latenza primo byte Indica il ritardo di tempo tra l'inizio dell'attività di sintesi e la ricezione del primo blocco di dati audio. SpeechServiceResponse_SynthesisFirstByteLatencyMs
latenza di fine Indica il ritardo di tempo tra l'inizio dell'attività di sintesi e la ricezione dei dati audio sintetizzati completi. SpeechServiceResponse_SynthesisFinishLatencyMs

L'SDK di Voce inserisce le durate della latenza nella raccolta Proprietà di SpeechSynthesisResult. Il codice di esempio seguente mostra questi valori.

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;
Latenza Descrizione Chiave della proprietà SpeechSynthesisResult
first byte latency Indica il ritardo di tempo tra l'inizio della sintesi e il primo blocco audio ricevuto. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Indica il ritardo di tempo tra l'inizio della sintesi e la ricezione dell'intero audio sintetizzato. SpeechServiceResponse_SynthesisFinishLatencyMs

L'SDK di Voce misura le latenze e le inserisce nel contenitore delle proprietà di SpeechSynthesisResult. Per ottenerle, fare riferimento ai codici seguenti.

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;
Latenza Descrizione Chiave della proprietà SpeechSynthesisResult
first byte latency Indica il ritardo di tempo tra l'inizio della sintesi e il primo blocco audio ricevuto. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Indica il ritardo di tempo tra l'inizio della sintesi e la ricezione dell'intero audio sintetizzato. SpeechServiceResponse_SynthesisFinishLatencyMs

L'SDK di Voce misura le latenze e le inserisce nel contenitore delle proprietà di SpeechSynthesisResult. Per ottenerle, fare riferimento ai codici seguenti.

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();
Latenza Descrizione Chiave della proprietà SpeechSynthesisResult
first byte latency Indica il ritardo di tempo tra l'inizio della sintesi e il primo blocco audio ricevuto. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Indica il ritardo di tempo tra l'inizio della sintesi e la ricezione dell'intero audio sintetizzato. SpeechServiceResponse_SynthesisFinishLatencyMs

L'SDK di Voce misura le latenze e le inserisce nel contenitore delle proprietà di SpeechSynthesisResult. Per ottenerle, fare riferimento ai codici seguenti.

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
Latenza Descrizione Chiave della proprietà SPXSpeechSynthesisResult
first byte latency Indica il ritardo di tempo tra l'inizio della sintesi e il primo blocco audio ricevuto. SPXSpeechServiceResponseSynthesisFirstByteLatencyMs
finish latency Indica il ritardo di tempo tra l'inizio della sintesi e la ricezione dell'intero audio sintetizzato. SPXSpeechServiceResponseSynthesisFinishLatencyMs

L'SDK di Voce misura le latenze e le inserisce nel contenitore delle proprietà di SPXSpeechSynthesisResult. Per ottenerle, fare riferimento ai codici seguenti.

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;

La latenza di primo byte è inferiore rispetto alla latenza di fine nella maggior parte dei casi. La latenza di primo byte è indipendente dalla lunghezza del testo, mentre la latenza di fine aumenta con la lunghezza del testo.

Idealmente, si vuole ridurre al minimo la latenza riscontrata dall'utente (la latenza prima che l'utente senta il suono) a un tempo di percorrenza della route di rete più la prima latenza audio in blocchi del servizio di sintesi vocale.

Streaming

Lo streaming è fondamentale per ridurre la latenza. Il codice client può avviare la riproduzione quando viene ricevuto il primo blocco audio. In uno scenario di servizio, è possibile inoltrare immediatamente i blocchi audio ai client invece di attendere l'intero audio.

È possibile usare PullAudioOutputStream, PushAudioOutputStream, l'Synthesizing evento e AudioDataStream dell'SDK di Voce per abilitare lo streaming.

Prendendo AudioDataStream come esempio:

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

È possibile usare PullAudioOutputStream, PushAudioOutputStream, l'Synthesizing evento e AudioDataStream dell'SDK di Voce per abilitare lo streaming.

Prendendo AudioDataStream come esempio:

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

È possibile usare PullAudioOutputStream, PushAudioOutputStream, l'Synthesizing evento e AudioDataStream dell'SDK di Voce per abilitare lo streaming.

Prendendo AudioDataStream come esempio:

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

È possibile usare PullAudioOutputStream, PushAudioOutputStream, l'Synthesizing evento e AudioDataStream dell'SDK di Voce per abilitare lo streaming.

Prendendo AudioDataStream come esempio:

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)

È possibile usare SPXPullAudioOutputStream, SPXPushAudioOutputStream, l'Synthesizing evento e SPXAudioDataStream dell'SDK di Voce per abilitare lo streaming.

Prendendo AudioDataStream come esempio:

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
}

Pre-connettere e riutilizzare SpeechSynthesizer

L'SDK di Voce usa un WebSocket per comunicare con il servizio. Idealmente, la latenza di rete deve essere un tempo di percorrenza della route (RTT). Se la connessione è stata appena stabilita, la latenza di rete include tempo aggiuntivo per stabilire la connessione. La creazione di una connessione WebSocket richiede l'handshake TCP, l'handshake SSL, la connessione HTTP e l'aggiornamento del protocollo, che introduce un ritardo di tempo. Per evitare la latenza di connessione, è consigliabile pre-connettersi e riutilizzare SpeechSynthesizer.

Pre-connessione

Per effettuare la pre-connessione, stabilire una connessione al servizio Voce quando si sa che la connessione è necessaria entro breve. Ad esempio, se si sta creando un bot vocale nel client, è possibile pre-connettersi al servizio di sintesi vocale quando l'utente inizia a parlare e chiamare SpeakTextAsync quando il testo di risposta del bot è pronto.

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

Nota

Se il testo è disponibile, è sufficiente chiamare SpeakTextAsync per sintetizzare l'audio. L'SDK gestirà la connessione.

Reuse SpeechSynthesizer

Un altro modo per ridurre la latenza di connessione consiste nel riutilizzare SpeechSynthesizer in modo da non dover creare una nuovo SpeechSynthesizer per ogni sintesi. È consigliabile usare il pool di oggetti nello scenario del servizio. Vedere il codice di esempio per C# e Java.

Trasmettere audio compresso sulla rete

Quando la rete è instabile o con larghezza di banda limitata, le dimensioni del payload influiscono anche sulla latenza. Nel frattempo, un formato audio compresso consente di far risparmiare larghezza di banda di rete agli utenti, cosa particolarmente utile per gli utenti di dispositivi mobili.

Sono supportati molti formati compressi, tra cui opus, webm, mp3, silk e così via. Vedere l'elenco completo in SpeechSynthesisOutputFormat. Ad esempio, la velocità in bit del formato Riff24Khz16BitMonoPcm è di 384 kbps, mentre Audio24Khz48KBitRateMonoMp3 è di solo 48 kbps. L'SDK di Voce usa automaticamente un formato compresso per la trasmissione quando viene impostato un formato di output pcm. Per Linux e Windows, GStreamer è necessario per abilitare la funzionalità. Fare riferimento a questa istruzione per installare e configurare GStreamer per l'SDK di Voce. Per Android, iOS e macOS, non è necessaria alcuna configurazione aggiuntiva a partire dalla versione 1.20.

Streaming di testo di input

Lo streaming di testo consente l'elaborazione di testo in tempo reale per la generazione rapida di audio. È perfetto per la vocalizzazione di testo dinamico, ad esempio la lettura di output da modelli di intelligenza artificiale come GPT in tempo reale. Questa funzionalità riduce al minimo la latenza e migliora la fluidità e la velocità di risposta degli output audio, rendendola ideale per applicazioni interattive, eventi live e dialoghi basati sull'intelligenza artificiale reattiva.

Come usare lo streaming di testo

Con l'SDK di Voce, lo streaming di testo è supportato in C#, C++ e Python.

Per usare la funzionalità di streaming di testo, connettersi all'endpoint websocket V2: wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

Vedere il codice di esempio per l'impostazione dell'endpoint:

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

Passaggi chiave

  1. Creare una richiesta di flusso di testo: usare SpeechSynthesisRequestInputType.TextStream per avviare un flusso di testo.

  2. Impostare le proprietà globali: regolare direttamente le impostazioni, ad esempio il formato di output e il nome della voce, perché la funzionalità gestisce input di testo parziali e non supporta SSML. Per istruzioni su come impostarle, vedere il codice di esempio seguente. Le voci di sintesi vocale OpenAI non sono supportate dalla funzionalità di streaming del testo. Vedere questa tabella lingua per il supporto completo della lingua.

    // Set output format
    speechConfig.SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm);
    
    // Set a voice name
    SpeechConfig.SetProperty(PropertyId.SpeechServiceConnection_SynthVoice, "en-US-AvaMultilingualNeural");
    
  3. Trasmettere il testo: per ogni blocco di testo generato da un modello GPT, usare request.InputStream.Write(text); per inviare il testo al flusso.

  4. Chiudere il flusso: dopo il completamento dell'output del modello GPT, chiudere il flusso usando request.InputStream.Close();.

Per un'implementazione dettagliata, vedere il codice di esempio in GitHub

Per usare la funzionalità di streaming di testo, connettersi all'endpoint websocket V2: wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

Vedere il codice di esempio per l'impostazione dell'endpoint:

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

Passaggi chiave

  1. Creare una richiesta di flusso di testo: usare speechsdk.SpeechSynthesisRequestInputType.TextStream per avviare un flusso di testo.

  2. Impostare le proprietà globali: regolare direttamente le impostazioni, ad esempio il formato di output e il nome della voce, perché la funzionalità gestisce input di testo parziali e non supporta SSML. Per istruzioni su come impostarle, vedere il codice di esempio seguente. Le voci di sintesi vocale OpenAI non sono supportate dalla funzionalità di streaming del testo. Vedere questa tabella lingua per il supporto completo della lingua.

    # set a voice name
    speech_config.speech_synthesis_voice_name = "en-US-AvaMultilingualNeural"
    
  3. Trasmettere il testo: per ogni blocco di testo generato da un modello GPT, usare request.input_stream.write(text) per inviare il testo al flusso.

  4. Chiudere il flusso: dopo il completamento dell'output del modello GPT, chiudere il flusso usando request.input_stream.close().

Per un'implementazione dettagliata, vedere il codice di esempio in GitHub.

Il codice di esempio C++ non è attualmente disponibile. Per il codice di esempio che mostra come usare lo streaming di testo, vedere:

Per il codice di esempio che mostra come usare lo streaming di testo, vedere:

Per il codice di esempio che mostra come usare lo streaming di testo, vedere:

Altri suggerimenti

Memorizzare nella cache i file CRL

SDK di Voce usa i file CRL per controllare la certificazione. La memorizzazione nella cache dei file CRL fino alla loro scadenza consente di evitare di scaricare i file CRL ogni volta. Per informazioni dettagliate, vedere Come configurare OpenSSL per Linux.

Usare l'SDK di Voce più recente

Continuiamo a migliorare le prestazioni dell'SDK Voce, provare quindi a usare quello più recente nell'applicazione.

Linee guida per i test di carico

È possibile usare il test di carico per testare la capacità del servizio di sintesi vocale e latenza. Ecco alcune linee guida:

  • Il servizio di sintesi vocale ha la possibilità di attivare il ridimensionamento automatico, ma richiede tempo aumentare il numero di istanze. Se la concorrenza viene aumentata in breve tempo, il client può ottenere una latenza prolungata o il codice di errore 429 (troppe richieste). È quindi consigliabile aumentare la concorrenza gradualmente nel test di carico. Vedere questo articolo per altri dettagli, in particolare questo esempio di modelli di carico di lavoro.
  • È possibile sfruttare l'esempio usando il pool di oggetti (C# e Java) per il test di carico e ottenere i numeri di latenza. È possibile modificare i turni di test e la concorrenza dell'esempio per soddisfare la concorrenza di destinazione.
  • Il servizio presenta una limitazione della quota in base al traffico reale, pertanto, se si vuole eseguire test di carico con la concorrenza più elevata rispetto al traffico reale, connettersi prima del test.

Passaggi successivi