Уменьшение задержки синтеза речи с помощью пакета SDK службы "Речь"

Задержка синтеза — критически важный фактор в любом сценарии применения. В этой статье мы рассмотрим рекомендации по снижению задержки и повышению производительности для конечных пользователей.

Обычно задержка измерятся параметрами first byte latency и finish latency следующим образом:

Задержка Description Ключ свойства SpeechSynthesisResult
задержка первого байта Обозначает временную задержку между началом задачи синтеза и получением первого фрагмента звуковых данных. SpeechServiceResponse_SynthesisFirstByteLatencyMs
задержка завершения Обозначает временную задержку между началом задачи синтеза и получением всего пакета синтезированных звуковых данных. SpeechServiceResponse_SynthesisFinishLatencyMs

В пакете SDK для службы "Речь" значения длительности задержки находятся в коллекции свойств SpeechSynthesisResult. Эти значения показаны в образце кода ниже.

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;
Задержка Description Ключ свойства SpeechSynthesisResult
first byte latency Обозначает временную задержку между началом задачи синтеза и получением первого фрагмента звуковых данных. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Обозначает временную задержку между началом задачи синтеза и получением всего пакета синтезированных звуковых данных. SpeechServiceResponse_SynthesisFinishLatencyMs

Пакет SDK службы "Речь" измеряет задержки и помещает их в контейнер свойств SpeechSynthesisResult. В коде ниже показано, как их получить.

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;
Задержка Description Ключ свойства SpeechSynthesisResult
first byte latency Обозначает временную задержку между началом задачи синтеза и получением первого фрагмента звуковых данных. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Обозначает временную задержку между началом задачи синтеза и получением всего пакета синтезированных звуковых данных. SpeechServiceResponse_SynthesisFinishLatencyMs

Пакет SDK службы "Речь" измеряет задержки и помещает их в контейнер свойств SpeechSynthesisResult. В коде ниже показано, как их получить.

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();
Задержка Description Ключ свойства SpeechSynthesisResult
first byte latency Обозначает временную задержку между началом задачи синтеза и получением первого фрагмента звуковых данных. SpeechServiceResponse_SynthesisFirstByteLatencyMs
finish latency Обозначает временную задержку между началом задачи синтеза и получением всего пакета синтезированных звуковых данных. SpeechServiceResponse_SynthesisFinishLatencyMs

Пакет SDK службы "Речь" измеряет задержки и помещает их в контейнер свойств SpeechSynthesisResult. В коде ниже показано, как их получить.

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
Задержка Description Ключ свойства SPXSpeechSynthesisResult
first byte latency Обозначает временную задержку между началом задачи синтеза и получением первого фрагмента звуковых данных. SPXSpeechServiceResponseSynthesisFirstByteLatencyMs
finish latency Обозначает временную задержку между началом задачи синтеза и получением всего пакета синтезированных звуковых данных. SPXSpeechServiceResponseSynthesisFinishLatencyMs

Пакет SDK службы "Речь" измеряет задержки и помещает их в контейнер свойств SPXSpeechSynthesisResult. В коде ниже показано, как их получить.

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;

Первая задержка байтов ниже, чем задержка завершения в большинстве случаев. Задержка первого байта не зависит от длины текста, а задержка завершения растет по мере увеличения этой длины.

В идеальном случае требуется минимизировать задержку для пользователя (время перед тем, как пользователь услышит звук) до времени однократного прохождения сетевого маршрута плюс задержка первого звукового фрагмента от службы синтеза речи.

Потоковая передача

Потоковая передача — важнейший фактор снижения задержки. Клиентский код может начать воспроизведение при получении первого звукового фрагмента. В сценарии обслуживания вы можете сразу пересылать клиентам звуковые фрагменты, не дожидаясь формирования всего звукового пакета.

Для реализации потоковой передачи можно использовать PullAudioOutputStream, PushAudioOutputStream, Synthesizing событие и AudioDataStream пакета SDK службы "Речь".

Рассмотрим AudioDataStream в качестве примера:

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

Для реализации потоковой передачи можно использовать PullAudioOutputStream, PushAudioOutputStream, Synthesizingсобытие и AudioDataStream пакета SDK службы "Речь".

Рассмотрим AudioDataStream в качестве примера:

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

Для реализации потоковой передачи можно использовать PullAudioOutputStream, PushAudioOutputStream, Synthesizingсобытие и AudioDataStream пакета SDK службы "Речь".

Рассмотрим AudioDataStream в качестве примера:

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

Для реализации потоковой передачи можно использовать PullAudioOutputStream, PushAudioOutputStream, Synthesizingсобытие и AudioDataStream пакета SDK службы "Речь".

Рассмотрим AudioDataStream в качестве примера:

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)

Для реализации потоковой передачи можно использовать SPXPullAudioOutputStream, SPXPushAudioOutputStream, Synthesizingсобытие и SPXAudioDataStream пакета SDK службы "Речь".

Рассмотрим AudioDataStream в качестве примера:

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
}

Предварительное подключение и повторное использование SpeechSynthesizer

Пакеты SDK для службы "Речь" используют WebSocket для обмена данными со службой. В идеале задержка сети должна равняться времени однократного прохождения сетевого маршрута плюс. Если подключение установлено, задержка сети включает дополнительное время для установки подключения. Для установки подключения WebSocket требуется подтверждение TCP, подтверждение SSL, HTTP-подключение и обновление протокола, что ведет к появлению задержки. Чтобы избежать задержки подключения, рекомендуется предварительно подключить и повторно использовать SpeechSynthesizer.

Перед подключением

Чтобы предварительно подключиться, установите подключение к службе "Речь", когда вы знаете, что подключение необходимо в ближайшее время. Например, если вы создаете бот речи в клиенте, вы можете предварительно подключиться к службе синтеза речи, когда пользователь начнет говорить, и вызвать SpeakTextAsync , когда текст ответа бота готов.

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

Примечание.

Если вам доступен текст для синтеза, просто вызовите функцию SpeakTextAsync, чтобы синтезировать звук. Пакет SDK выполнит установку подключения.

Повторное использование SpeechSynthesizer

Снизить задержку подключения также можно путем повторного использования SpeechSynthesizer, чтобы не создавать новый объект SpeechSynthesizer для каждой операции синтеза. Мы рекомендуем использовать пул объектов в сценарии обслуживания (см. наш пример кода для C# и Java).

Передача сжатого звука по сети

Если сеть нестабильная или ограниченная пропускная способность, размер полезных данных также влияет на задержку. В то же время сжатие аудио позволяет уменьшить нагрузку на пропускную способность сети, что особенно важно для мобильных пользователей.

Поддерживается множество форматов сжатия, включая opus, webm, mp3, silk и т. п. (полный список см. в разделе SpeechSynthesisOutputFormat). Например, скорость для формата Riff24Khz16BitMonoPcm составляет 384 кбит/с, а для Audio24Khz48KBitRateMonoMp3 — всего 48 кбит/с. Наш пакет SDK службы "Речь" будет автоматически использовать сжатый формат для передачи, если задан выходной формат pcm. В Linux и Windows для включения этой функции потребуется GStreamer. Инструкции по установке и настройке GStreamer пакета SDK для службы "Речь" см. здесь. Для Android, iOS и macOS дополнительная настройка не требуется, начиная с версии 1.20.

Другие советы

Кэширование CRL-файлов

Пакет SDK для службы "Речь" использует файлы списков отзыва сертификатов (CRL) для проверки сертификата. Хранение CRL-файлов в кэше до истечения срока их действия помогает избежать их повторной загрузки каждый раз. Дополнительные сведения см. в статье Настройка OpenSSL для Linux.

Использование последней версии пакета SDK для службы "Речь"

Мы постоянно улучшаем производительность пакета SDK для службы "Речь", поэтому рекомендуем вам использовать его последнюю версию.

Советы в отношении нагрузочного теста

С помощью нагрузочного теста можно проверить емкость и задержку службы синтеза речи. Ниже приведены некоторые рекомендации.

  • Служба синтеза речи имеет возможность автомасштабирования, но требует времени для горизонтального масштабирования. Если параллелизм увеличивается в течение короткого времени, клиент может получить длинную задержку или 429 код ошибки (слишком много запросов). Поэтому мы рекомендуем пошагово увеличивать параллелизм в ходе нагрузочного теста. Дополнительные сведения см. в этой статье, особенно этот пример шаблонов рабочей нагрузки.
  • Вы можете использовать наш пример с помощью пула объектов (C# и Java) для нагрузочного теста и получения чисел задержки. Вы можете изменить шаги и параллелизм тестов в примере в соответствии с целевым параллелизмом.
  • Служба имеет ограничение квоты на основе реального трафика, поэтому если вы хотите выполнить нагрузочный тест с параллелизмом выше реального трафика, подключитесь перед тестом.

Следующие шаги