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
Créer une requête de flux de texte : utilisez
SpeechSynthesisRequestInputType.TextStream
pour lancer un flux de texte.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");
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.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
Créer une requête de flux de texte : utilisez
speechsdk.SpeechSynthesisRequestInputType.TextStream
pour lancer un flux de texte.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"
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.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
- Voir les exemples sur GitHub