Menor latência de síntese de fala usando o SDK de Fala

Neste artigo, apresentamos as práticas recomendadas para reduzir a latência da sintetização de conversão de texto em fala e trazer o melhor desempenho aos usuários finais.

Normalmente, medimos a latência por first byte latency e finish latency, da seguinte forma:

Latency Descrição Chave de propriedade SpeechSynthesisResult
latência de primeiro byte Indica o atraso de tempo entre o início da tarefa de síntese e o recebimento da primeira parte dos dados de áudio. SpeechServiceResponse_SynthesisFirstByteLatencyMs
latência de conclusão Indica o atraso de tempo entre o início da tarefa de síntese e o recebimento de todos os dados de áudio sintetizados. SpeechServiceResponse_SynthesisFinishLatencyMs

O SDK de Fala coloca as durações de latência na coleção Propriedades de SpeechSynthesisResult. O código de exemplo a seguir mostra esses valores.

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;
Latency Descrição Chave de propriedade SpeechSynthesisResult
first byte latency Indica o atraso de tempo entre o início da síntese e o momento quando a primeira parte de áudio é recebida. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Indica o atraso de tempo entre o início da síntese e momento quando todo o áudio sintetizado é recebido. SpeechServiceResponse_SynthesisFinishLatencyMs

O SDK de Fala mediu as latências e as colocou no pacote de propriedades de SpeechSynthesisResult. Consulte os códigos a seguir para obtê-los.

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;
Latency Descrição Chave de propriedade SpeechSynthesisResult
first byte latency Indica o atraso de tempo entre o início da síntese e o momento quando a primeira parte de áudio é recebida. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Indica o atraso de tempo entre o início da síntese e momento quando todo o áudio sintetizado é recebido. SpeechServiceResponse_SynthesisFinishLatencyMs

O SDK de Fala mediu as latências e as colocou no pacote de propriedades de SpeechSynthesisResult. Consulte os códigos a seguir para obtê-los.

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();
Latency Descrição Chave de propriedade SpeechSynthesisResult
first byte latency Indica o atraso de tempo entre o início da síntese e o momento quando a primeira parte de áudio é recebida. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Indica o atraso de tempo entre o início da síntese e momento quando todo o áudio sintetizado é recebido. SpeechServiceResponse_SynthesisFinishLatencyMs

O SDK de Fala mediu as latências e as colocou no pacote de propriedades de SpeechSynthesisResult. Consulte os códigos a seguir para obtê-los.

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
Latency Descrição Chave de propriedade SPXSpeechSynthesisResult
first byte latency Indica o atraso de tempo entre o início da síntese e o momento quando a primeira parte de áudio é recebida. SPXSpeechServiceResponseSynthesisFirstByteLatencyMs
finish latency Indica o atraso de tempo entre o início da síntese e momento quando todo o áudio sintetizado é recebido. SPXSpeechServiceResponseSynthesisFinishLatencyMs

O SDK de Fala mediu as latências e as colocou no pacote de propriedades de SPXSpeechSynthesisResult. Consulte os códigos a seguir para obtê-los.

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;

A latência do primeiro byte é menor do que a latência de conclusão na maioria dos casos. A latência do primeiro byte é independente do comprimento do texto, enquanto a latência de conclusão aumenta com o comprimento do texto.

O ideal é minimizar a latência de experiência do usuário (a latência antes que o usuário ouça o som) para um tempo de viagem de rota de rede mais a latência da primeira parte do áudio do serviço de síntese de fala.

Streaming

O streaming é essencial para reduzir a latência. O código do cliente pode iniciar a reprodução quando a primeira parte do áudio é recebida. Em um cenário de serviço, você pode encaminhar imediatamente as partes de áudio para seus clientes em vez de aguardar o áudio todo.

Você pode usar PullAudioOutputStream, PushAudioOutputStream, evento Synthesizing e AudioDataStream do SDK de Fala para habilitar o streaming.

Tomando AudioDataStream como exemplo:

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

Você pode usar o PullAudioOutputStream, PushAudioOutputStream, o Synthesizingevento e o AudioDataStream do SDK de Fala para habilitar o streaming.

Tomando AudioDataStream como exemplo:

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

Você pode usar o PullAudioOutputStream, PushAudioOutputStream, o Synthesizingevento e o AudioDataStream do SDK de Fala para habilitar o streaming.

Tomando AudioDataStream como exemplo:

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

Você pode usar o PullAudioOutputStream, PushAudioOutputStream, o Synthesizingevento e o AudioDataStream do SDK de Fala para habilitar o streaming.

Tomando AudioDataStream como exemplo:

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)

Você pode usar o SPXPullAudioOutputStream, SPXPushAudioOutputStream, o Synthesizingevento e o SPXAudioDataStream do SDK de Fala para habilitar o streaming.

Tomando AudioDataStream como exemplo:

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
}

Pré-conectar e reutilizar o SpeechSynthesizer

O SDK de Fala usa um websocket para se comunicar com o serviço. O ideal é que a latência de rede seja de um RTT (tempo de viagem de rota). Se a conexão tiver sido estabelecida recentemente, a latência de rede inclui tempo extra para estabelecer a conexão. O estabelecimento de uma conexão websocket precisa do handshake TCP, do handshake SSL, da conexão HTTP e da atualização do protocolo, o que introduz atraso de tempo. Para evitar a latência de conexão, recomendamos a pré-conexão e a reutilização do SpeechSynthesizer.

Antes de conectar-se

Para fazer a pré-conexão, estabeleça uma conexão com o serviço de Fala quando você souber que a conexão será necessária em breve. Por exemplo, se você estiver criando um bot de fala no cliente, poderá fazer a pré-conexão ao serviço de síntese de fala quando o usuário começar a falar, e chamar SpeakTextAsync quando o texto de resposta do bot estiver 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];

Observação

Se o texto estiver disponível, chame o SpeakTextAsync para sintetizar o áudio. O SDK manipulará a conexão.

Reutilizar o SpeechSynthesizer

Outra maneira de reduzir a latência de conexão é reutilizar o SpeechSynthesizer para que você não precise criar um novo SpeechSynthesizer para cada síntese. É recomendável usar o pool de objetos no cenário de serviço. Confira nosso código de exemplo para C# e Java.

Transmitir áudio compactado pela rede

Quando a rede está instável ou com largura de banda limitada, o tamanho do conteúdo também afeta a latência. Enquanto isso, um formato de áudio compactado ajuda a economizar a largura de banda de rede dos usuários, o que é especialmente valioso para usuários de dispositivos móveis.

Damos suporte a muitos formatos compactados, incluindo opus, webm, mp3, silk, e outros. Consulte a lista completa em SpeechSynthesisOutputFormat. Por exemplo, a taxa de bits do formato Riff24Khz16BitMonoPcm é de 384 kbps, enquanto Audio24Khz48KBitRateMonoMp3 é de apenas 48 kbps. O SDK de Fala usa automaticamente um formato compactado para transmissão quando um formato de saída pcm é definido. Para o Linux e o Windows, o GStreamer é necessário habilitar esse recurso. Consulte esta instrução para instalar e configurar GStreamer para o SDK de Fala. Para Android, iOS e macOS, nenhuma configuração extra é necessária a partir da versão 1.20.

Streaming de texto de entrada

O streaming de texto permite o processamento de texto em tempo real para a geração rápida de áudio. É perfeito para vocalização dinâmica de texto, como ler saídas de modelos de IA como o GPT em tempo real. Esse recurso minimiza a latência e melhora a fluidez e a capacidade de resposta das saídas de áudio, tornando-o ideal para aplicativos interativos, eventos ao vivo e diálogos responsivos impulsionados por IA.

Como usar o streaming de texto

Há suporte para streaming de texto em C#, C++ e Python com o SDK de Fala.

Para usar o recurso de streaming de texto, conecte-se ao ponto de extremidade websocket V2: wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

Consulte o código de exemplo para definir o ponto de extremidade:

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

Principais etapas

  1. Crie uma solicitação de fluxo de texto: use SpeechSynthesisRequestInputType.TextStream para iniciar um fluxo de texto.

  2. Defina propriedades globais: ajuste configurações como formato de saída e nome da voz diretamente, pois o recurso lida com entradas de texto parciais e não dá suporte a SSML. Consulte o código de exemplo a seguir para obter instruções sobre como configurá-los. O recurso de streaming de texto não dá suporte à conversão de texto em fala do OpenAI. Consulte esta tabela de idiomas para obter suporte completo ao idioma.

    // Set output format
    speechConfig.SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm);
    
    // Set a voice name
    SpeechConfig.SetProperty(PropertyId.SpeechServiceConnection_SynthVoice, "en-US-AvaMultilingualNeural");
    
  3. Transmita seu texto: para cada parte de texto gerado por um modelo GPT, use request.InputStream.Write(text); para enviar o texto para o fluxo.

  4. Feche o fluxo: depois que o modelo GPT concluir sua saída, feche o fluxo usando request.InputStream.Close();.

Para obter uma implementação detalhada, consulte o código de exemplo no GitHub

Para usar o recurso de streaming de texto, conecte-se ao ponto de extremidade websocket V2: wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

Consulte o código de exemplo para definir o ponto de extremidade:

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

Principais etapas

  1. Crie uma solicitação de fluxo de texto: use speechsdk.SpeechSynthesisRequestInputType.TextStream para iniciar um fluxo de texto.

  2. Defina propriedades globais: ajuste configurações como formato de saída e nome da voz diretamente, pois o recurso lida com entradas de texto parciais e não dá suporte a SSML. Consulte o código de exemplo a seguir para obter instruções sobre como configurá-los. O recurso de streaming de texto não dá suporte à conversão de texto em fala do OpenAI. Consulte esta tabela de idiomas para obter suporte completo ao idioma.

    # set a voice name
    speech_config.speech_synthesis_voice_name = "en-US-AvaMultilingualNeural"
    
  3. Transmita seu texto: para cada parte de texto gerado por um modelo GPT, use request.input_stream.write(text) para enviar o texto para o fluxo.

  4. Feche o fluxo: depois que o modelo GPT concluir sua saída, feche o fluxo usando request.input_stream.close().

Para obter uma implementação detalhada, consulte o código de exemplo no GitHub.

O código de exemplo em C++ não está disponível no momento. Para obter o código de exemplo que mostra como usar o streaming de texto, consulte:

Para obter o código de exemplo que mostra como usar o streaming de texto, consulte:

Para obter o código de exemplo que mostra como usar o streaming de texto, consulte:

Outras dicas

Armazenar arquivos CRL em cache

O SDK de Fala usa arquivos CRL para verificar a certificação. Armazenar arquivos CRL em cache até que eles expirem ajuda a evitar o download desses arquivos a cada vez que os usar. Confira Como configurar o OpenSSL para Linux para conhecer mais detalhes.

Usar o SDK de Fala mais recente

Estamos continuamente melhorando o desempenho do SDK de Fala; portanto, procure usar o SDK de Fala mais recente em seu aplicativo.

Diretriz de teste de carga

Você pode usar o teste de carga para testar a capacidade e a latência do serviço de síntese de fala. Aqui estão algumas diretrizes:

  • O serviço de síntese de fala tem a capacidade de dimensionar automaticamente, mas leva tempo para escalar horizontalmente. Se a simultaneidade for aumentada em um curto período de tempo, o cliente poderá enfrentar longa latência ou obter o código de erro 429 (muitas solicitações). Portanto, recomendamos que você aumente sua simultaneidade aos poucos no teste de carga. Consulte este artigo para obter mais detalhes, especialmente este exemplo de padrões de carga de trabalho.
  • Você pode usar nosso exemplo usando o pool de objetos (C# e Java) para teste de carga e para obter os números de latência. Você pode modificar os turnos de teste e a simultaneidade no exemplo para atender à sua simultaneidade de destino.
  • O serviço tem uma limitação de cota com base no tráfego real, portanto, se você quiser executar o teste de carga com a simultaneidade maior do que o tráfego real, conecte-se antes do teste.

Próximas etapas