Поделиться через


Уменьшение задержки синтеза речи с помощью пакета 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.

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

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

Использование потоковой передачи текста

Потоковая передача текста поддерживается в C#, C++ и Python с помощью пакета SDK службы "Речь".

Чтобы использовать функцию потоковой передачи текста, подключитесь к конечной точке websocket версии 2: wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

См. пример кода для настройки конечной точки:

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

Ключевые шаги

  1. Создайте текстовый запрос потока: используйте SpeechSynthesisRequestInputType.TextStream для запуска текстового потока.

  2. Задайте глобальные свойства: настройте такие параметры, как формат вывода и имя голоса напрямую, так как функция обрабатывает частичные текстовые входные данные и не поддерживает SSML. Ознакомьтесь со следующим примером кода, чтобы узнать, как задать их. Текст OpenAI для голоса речи не поддерживается функцией потоковой передачи текста. См. эту таблицу языков для полной поддержки языка.

    // Set output format
    speechConfig.SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm);
    
    // Set a voice name
    SpeechConfig.SetProperty(PropertyId.SpeechServiceConnection_SynthVoice, "en-US-AvaMultilingualNeural");
    
  3. Потоковая передача текста: для каждого фрагмента текста, созданного из модели GPT, используйте request.InputStream.Write(text); для отправки текста в поток.

  4. Закройте поток: после завершения выходных данных модели GPT закройте поток с помощью request.InputStream.Close();.

Подробные сведения о реализации см. в примере кода на GitHub

Чтобы использовать функцию потоковой передачи текста, подключитесь к конечной точке websocket версии 2: wss://{region}.tts.speech.microsoft.com/cognitiveservices/websocket/v2

См. пример кода для настройки конечной точки:

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

Ключевые шаги

  1. Создайте текстовый запрос потока: используйте speechsdk.SpeechSynthesisRequestInputType.TextStream для запуска текстового потока.

  2. Задайте глобальные свойства: настройте такие параметры, как формат вывода и имя голоса напрямую, так как функция обрабатывает частичные текстовые входные данные и не поддерживает SSML. Ознакомьтесь со следующим примером кода, чтобы узнать, как задать их. Текст OpenAI для голоса речи не поддерживается функцией потоковой передачи текста. См. эту таблицу языков для полной поддержки языка.

    # set a voice name
    speech_config.speech_synthesis_voice_name = "en-US-AvaMultilingualNeural"
    
  3. Потоковая передача текста: для каждого фрагмента текста, созданного из модели GPT, используйте request.input_stream.write(text) для отправки текста в поток.

  4. Закройте поток: после завершения выходных данных модели GPT закройте поток с помощью request.input_stream.close().

Подробные сведения о реализации см. в примере кода на сайте GitHub.

Пример кода C++ сейчас недоступен. Пример кода, демонстрирующий использование потоковой передачи текста, см. в следующих примерах:

Пример кода, демонстрирующий использование потоковой передачи текста, см. в следующих примерах:

Пример кода, демонстрирующий использование потоковой передачи текста, см. в следующих примерах:

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

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

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

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

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

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

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

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

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