Réduire la latence de synthèse vocale avec le SDK Speech
La latence de synthèse est essentielle pour vos applications. Dans cet article, nous allons présenter les meilleures pratiques qui permettent de réduire la latence et d’offrir à vos utilisateurs finaux les meilleures performances.
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 synthétisé 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 un pool d’objets dans le scénario de 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.
Notre 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 à partir de la version 1.20.
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