Partager via


Réduire la latence de synthèse vocale avec le SDK Speech

Dans cet article, nous présentons les meilleures pratiques qui permettent de réduire la latence de la synthèse vocale et d’offrir les meilleures performances à vos utilisateurs finaux.

Normalement, nous mesurons la latence par first byte latency et finish latency, comme suit :

Latence Description Clé de propriété SpeechSynthesisResult
first byte latency (latence du premier octet) Indique l’intervalle de temps entre le début de la tâche de synthèse et la réception du premier bloc de données audio. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency (latence de fin) Indique l’intervalle de temps entre le début de la tâche de synthèse et la réception de l’intégralité des données audio synthétisées. SpeechServiceResponse_SynthesisFinishLatencyMs

Le SDK Speech place les durées de latence dans la collection Properties de SpeechSynthesisResult. L’exemple de code suivant illustre ces valeurs.

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;
Latence Description Clé de propriété SpeechSynthesisResult
first byte latency Indique l’intervalle de temps entre le début de la synthèse et la réception du premier bloc audio. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Indique l’intervalle de temps entre le début de la synthèse et la réception de l’intégralité de l’audio synthétisé. SpeechServiceResponse_SynthesisFinishLatencyMs

Le SDK Speech a mesuré les latences et les a placées dans le jeu de propriétés de SpeechSynthesisResult. Reportez-vous aux codes suivants pour les obtenir.

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;
Latence Description Clé de propriété SpeechSynthesisResult
first byte latency Indique l’intervalle de temps entre le début de la synthèse et la réception du premier bloc audio. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Indique l’intervalle de temps entre le début de la synthèse et la réception de l’intégralité de l’audio synthétisé. SpeechServiceResponse_SynthesisFinishLatencyMs

Le SDK Speech a mesuré les latences et les a placées dans le jeu de propriétés de SpeechSynthesisResult. Reportez-vous aux codes suivants pour les obtenir.

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();
Latence Description Clé de propriété SpeechSynthesisResult
first byte latency Indique l’intervalle de temps entre le début de la synthèse et la réception du premier bloc audio. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Indique l’intervalle de temps entre le début de la synthèse et la réception de l’intégralité de l’audio synthétisé. SpeechServiceResponse_SynthesisFinishLatencyMs

Le SDK Speech a mesuré les latences et les a placées dans le jeu de propriétés de SpeechSynthesisResult. Reportez-vous aux codes suivants pour les obtenir.

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
Latence Description Clé de propriété SPXSpeechSynthesisResult
first byte latency Indique l’intervalle de temps entre le début de la synthèse et la réception du premier bloc audio. SPXSpeechServiceResponseSynthesisFirstByteLatencyMs
finish latency Indique l’intervalle de temps entre le début de la synthèse et la réception de l’intégralité de l’audio synthétisé. SPXSpeechServiceResponseSynthesisFinishLatencyMs

Le SDK Speech a mesuré les latences et les a placées dans le jeu de propriétés de SPXSpeechSynthesisResult. Reportez-vous aux codes suivants pour les obtenir.

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;

Dans la plupart des cas, la latence du premier octet est plus faible que la latence de fin. La latence du premier octet est indépendante de la longueur du texte, tandis que la latence de fin augmente avec la longueur du texte.

Idéalement, nous voulons minimiser la latence expérimentée par l’utilisateur (la latence qui se produit avant que l’utilisateur entende le son) à une seule durée aller-retour réseau additionnée à la latence du premier bloc audio du service de synthèse vocale.

Diffusion en continu

Le streaming est essentiel pour réduire la latence. Le code client peut commencer la lecture dès que le premier bloc audio est reçu. Dans un scénario de service, vous pouvez transférer les blocs audio immédiatement à vos clients au lieu d’attendre l’intégralité de l’audio.

Vous pouvez utiliser PullAudioOutputStream, PushAudioOutputStream, l’événement Synthesizing et AudioDataStream du SDK Speech pour activer le streaming.

Prenons AudioDataStream comme exemple :

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

Vous pouvez utiliser PullAudioOutputStream, PushAudioOutputStream, l’événement Synthesizing et AudioDataStream du SDK Speech pour activer le streaming.

Prenons AudioDataStream comme exemple :

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

Vous pouvez utiliser PullAudioOutputStream, PushAudioOutputStream, l’événement Synthesizing et AudioDataStream du SDK Speech pour activer le streaming.

Prenons AudioDataStream comme exemple :

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

Vous pouvez utiliser PullAudioOutputStream, PushAudioOutputStream, l’événement Synthesizing et AudioDataStream du SDK Speech pour activer le streaming.

Prenons AudioDataStream comme exemple :

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)

Vous pouvez utiliser SPXPullAudioOutputStream, SPXPushAudioOutputStream, l’événement Synthesizing et SPXAudioDataStream du SDK Speech pour activer le streaming.

Prenons AudioDataStream comme exemple :

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éconnecter et réutiliser SpeechSynthesizer

Le SDK Speech utilise un websocket pour communiquer avec le service. Idéalement, la latence réseau doit correspondre à une seule durée aller-retour (RTT). Si la connexion vient d’être établie, la latence réseau inclut du temps supplémentaire pour établir la connexion. L’établissement d’une connexion websocket nécessite l’établissement d’une liaison TCP, l’établissement d’une liaison SSL, la connexion HTTP et la mise à niveau du protocole, ce qui introduit un délai. Pour éviter la latence de la connexion, nous vous recommandons de préconnecter et réutiliser SpeechSynthesizer.

Avant la connexion

Pour la préconnexion, établissez une connexion au service Speech quand vous savez que la connexion sera bientôt nécessaire. Par exemple, si vous générez un bot de message dans le client, vous pouvez vous préconnecter au service de synthèse vocale quand l’utilisateur commence à parler, puis appeler SpeakTextAsync quand le texte de réponse du bot est prêt.

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

Remarque

Si le texte est disponible, appelez simplement SpeakTextAsync pour synthétiser l’audio. Le SDK va gérer la connexion.

Réutiliser SpeechSynthesizer

Une autre façon de réduire la latence de la connexion consiste à réutiliser SpeechSynthesizer afin de ne pas avoir besoin de créer un SpeechSynthesizer pour chaque synthèse. Nous vous recommandons d’utiliser le pool d’objets dans le scénario du service. Consultez notre exemple de code pour C# et Java.

Transmettre l’audio compressé sur le réseau

Quand le réseau est instable ou sa bande passante limitée, la taille de la charge utile affecte également la latence. D’un autre côté, un format audio compressé permet d’économiser la bande passante réseau des utilisateurs, ce qui s’avère particulièrement précieux pour les utilisateurs mobiles.

Nous prenons en charge de nombreux formats compressés, notamment opus, webm, mp3, silk, etc. Consultez la liste complète dans SpeechSynthesisOutputFormat. Par exemple, la vitesse de transmission du format Riff24Khz16BitMonoPcm s’élève à 384 Kbits/s, tandis que Audio24Khz48KBitRateMonoMp3 ne coûte que 48 Kbits/s. Le Kit de développement logiciel (SDK) Speech utilise automatiquement un format compressé pour la transmission quand un format de sortie pcm est défini. Pour Linux et Windows, GStreamer est requis pour activer cette fonctionnalité. Reportez-vous à cette instruction pour installer et configurer GStreamer pour le SDK Speech. Pour Android, iOS et macOS, aucune configuration supplémentaire n’est nécessaire à compter de la version 1.20.

Streaming de texte d’entrée

Le streaming de texte permet un traitement du texte en temps réel pour une génération audio rapide. Il est parfait pour la vocalisation dynamique de textes, comme la lecture en temps réel des sorties de modèles d’IA tels que GPT. Cette fonctionnalité réduit la latence et améliore la fluidité et la réactivité des sorties audio, ce qui la rend idéale pour les applications interactives, les événements en direct et les dialogues réactifs pilotés par l’IA.

Comment utiliser le streaming de texte

Le streaming de texte est pris en charge dans C#, C++ et Python avec le Kit de développement logiciel (SDK) Speech.

Pour utiliser la fonctionnalité de streaming de texte, connectez-vous au point de terminaison WebSocket V2 : wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

Consultez l’exemple de code pour définir le point de terminaison :

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

Étapes clés

  1. Créer une requête de flux de texte : utilisez SpeechSynthesisRequestInputType.TextStream pour lancer un flux de texte.

  2. Définir les propriétés globales : ajustez directement des paramètres tels que le format de sortie et le nom de la voix, car la fonctionnalité gère les entrées de texte partielles et ne prend pas en charge SSML. Reportez-vous à l’exemple de code suivant pour obtenir des instructions sur la façon de les définir. Les voix de la synthèse vocale OpenAI ne sont pas prises en charge par la fonctionnalité de streaming de texte. Consultez ce tableau des langues pour connaître la prise en charge complète des langues.

    // Set output format
    speechConfig.SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm);
    
    // Set a voice name
    SpeechConfig.SetProperty(PropertyId.SpeechServiceConnection_SynthVoice, "en-US-AvaMultilingualNeural");
    
  3. Diffuser votre texte en continu : pour chaque bloc de texte généré depuis un modèle GPT, utilisez request.InputStream.Write(text); pour envoyer le texte au flux.

  4. Fermer le flux : une fois que le modèle GPT a terminé sa sortie, fermez le flux en utilisant request.InputStream.Close();.

Pour obtenir une implémentation détaillée, consultez l’exemple de code sur GitHub.

Pour utiliser la fonctionnalité de streaming de texte, connectez-vous au point de terminaison WebSocket V2 : wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

Consultez l’exemple de code pour définir le point de terminaison :

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

Étapes clés

  1. Créer une requête de flux de texte : utilisez speechsdk.SpeechSynthesisRequestInputType.TextStream pour lancer un flux de texte.

  2. Définir les propriétés globales : ajustez directement des paramètres tels que le format de sortie et le nom de la voix, car la fonctionnalité gère les entrées de texte partielles et ne prend pas en charge SSML. Reportez-vous à l’exemple de code suivant pour obtenir des instructions sur la façon de les définir. Les voix de la synthèse vocale OpenAI ne sont pas prises en charge par la fonctionnalité de streaming de texte. Consultez ce tableau des langues pour connaître la prise en charge complète des langues.

    # set a voice name
    speech_config.speech_synthesis_voice_name = "en-US-AvaMultilingualNeural"
    
  3. Diffuser votre texte en continu : pour chaque bloc de texte généré depuis un modèle GPT, utilisez request.input_stream.write(text) pour envoyer le texte au flux.

  4. Fermer le flux : une fois que le modèle GPT a terminé sa sortie, fermez le flux en utilisant request.input_stream.close().

Pour obtenir une implémentation détaillée, consultez l’exemple de code sur GitHub.

L’exemple de code C++ n’est pas disponible actuellement. Pour obtenir l’exemple de code qui montre comment utiliser le streaming de texte, consultez :

Pour obtenir l’exemple de code qui montre comment utiliser le streaming de texte, consultez :

Pour obtenir l’exemple de code qui montre comment utiliser le streaming de texte, consultez :

Autres conseils

Mettre en cache les fichiers de liste de révocation de certificats

Le SDK Speech utilise des fichiers de liste de révocation de certificats pour vérifier la certification. La mise en cache de ces fichiers jusqu’à leur expiration vous permet d’éviter de les télécharger à chaque fois. Pour plus d’informations, consultez Guide pratique pour configurer OpenSSL pour Linux.

Utiliser le dernier SDK Speech

Nous améliorons sans cesse les performances du SDK Speech, alors essayez d’utiliser le tout dernier dans votre application.

Instructions de test de charge

Vous pouvez utiliser un test de charge pour tester la capacité et la latence du service de synthèse vocale. Voici quelques recommandations :

  • Le service de synthèse vocale a la capacité à se mettre à l’échelle automatiquement, mais le scale-out prend du temps. Si l’accès concurrentiel augmente en un court laps de temps, le client peut observer une longue latence ou un code d’erreur 429 (trop de requêtes). Ainsi, nous vous recommandons d’augmenter votre concurrence progressivement lors du test de charge. Pour plus de détails, consultez cet article, notamment cet exemple de modèles de charge de travail.
  • Vous pouvez recourir à notre exemple en utilisant le pool d’objets (C# etJava) pour le test de charge et obtenir les chiffres de latence. Vous pouvez modifier les tours de test et l’accès concurrentiel dans l’exemple pour répondre à votre accès concurrentiel cible.
  • Le service a une limite de quota en fonction du trafic réel. Par conséquent, si vous voulez effectuer un test de charge avec une concurrence supérieure à votre trafic réel, connectez-vous avant votre test.

Étapes suivantes