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