Genera audio en tiempo real

Esta guía te muestra cómo generar audio en tiempo real mediante una conexión WebSocket.

La transmisión por WebSocket es un método para enviar y recibir datos a través de una única conexión persistente. Este método resulta útil para aplicaciones en tiempo real en las que necesitas transmitir datos de audio a medida que están disponibles.

Si quieres probar rápidamente la latencia (tiempo hasta el primer byte) de una conexión WebSocket con la API de Texto a Voz de ElevenLabs, puedes instalar elevenlabs-latency mediante npm y seguir las instrucciones aquí.

Los WebSockets están disponibles para Texto a Voz y la plataforma de Agents. Esta guía trata sobre el WebSocket de Texto a Voz (/v1/text-to-speech/{voice_id}/stream-input). Esa ruta de API no es compatible con los modelos eleven_v3 ni eleven_v4. Para diálogos con Eleven v3 o Eleven v4 a través de WebSocket, consulta Texto a Diálogo en Tiempo Real y WebSockets de Texto a Voz frente a Texto a Diálogo.

Requisitos

  • Una cuenta de ElevenLabs con una clave de API (aquí tienes cómo encontrar tu clave de API).
  • Python o Node.js (u otro entorno de ejecución de JavaScript) instalado en tu equipo

Configuración

Instala las dependencias necesarias:

pip install python-dotenv
pip install websockets

A continuación, crea un archivo .env en el directorio de tu proyecto y añade tu clave de API:

.env
ELEVENLABS_API_KEY=your_elevenlabs_api_key_here

Inicia la conexión WebSocket

Después de elegir una voz de la Biblioteca de voces y el modelo de texto a voz que quieres usar, inicia una conexión WebSocket con la API de texto a voz.

import os
from dotenv import load_dotenv
import websockets
# Load the API key from the .env file
load_dotenv()
ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
voice_id = 'Xb7hH8MSUJpSbSDYk0k2'
# For use cases where latency is important, we recommend using the 'eleven_flash_v2_5' model.
model_id = 'eleven_flash_v2_5'
async def text_to_speech_ws_streaming(voice_id, model_id):
uri = f"wss://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream-input?model_id={model_id}"
async with websockets.connect(uri) as websocket:
...

Envía el texto de entrada

Cuando se abra la conexión WebSocket, configura primero los ajustes de voz. A continuación, envía el mensaje de texto a la API.

async def text_to_speech_ws_streaming(voice_id, model_id):
async with websockets.connect(uri) as websocket:
await websocket.send(json.dumps({
"text": " ",
"voice_settings": {"stability": 0.5, "similarity_boost": 0.8, "use_speaker_boost": False},
"generation_config": {
"chunk_length_schedule": [120, 160, 250, 290]
},
"xi_api_key": ELEVENLABS_API_KEY,
}))
text = "The twilight sun cast its warm golden hues upon the vast rolling fields, saturating the landscape with an ethereal glow. Silently, the meandering brook continued its ceaseless journey, whispering secrets only the trees seemed privy to."
await websocket.send(json.dumps({"text": text}))
# Send empty string to indicate the end of the text sequence which will close the WebSocket connection
await websocket.send(json.dumps({"text": ""}))

Guarda el audio en un archivo

Lee el mensaje entrante de la conexión WebSocket y escribe los fragmentos de audio en un archivo local.

import asyncio
async def write_to_local(audio_stream):
"""Write the audio encoded in base64 string to a local mp3 file."""
with open(f'./output/test.mp3', "wb") as f:
async for chunk in audio_stream:
if chunk:
f.write(chunk)
async def listen(websocket):
"""Listen to the websocket for audio data and stream it."""
while True:
try:
message = await websocket.recv()
data = json.loads(message)
if data.get("audio"):
yield base64.b64decode(data["audio"])
elif data.get('isFinal'):
break
except websockets.exceptions.ConnectionClosed:
print("Connection closed")
break
async def text_to_speech_ws_streaming(voice_id, model_id):
async with websockets.connect(uri) as websocket:
...
# Add listen task to submit the audio chunks to the write_to_local function
listen_task = asyncio.create_task(write_to_local(listen(websocket)))
await listen_task
asyncio.run(text_to_speech_ws_streaming(voice_id, model_id))

Ejecuta el script

Puedes ejecutar el script con el siguiente comando en tu terminal. Se guardará un archivo de audio mp3 en el directorio output.

python text-to-speech-websocket.py

Configuración avanzada

El uso de WebSockets incluye algunos ajustes avanzados que puedes utilizar para afinar la generación de audio en tiempo real.

Búfer

Al generar audio en tiempo real, debes tener en cuenta dos conceptos importantes: tiempo hasta el primer byte (TTFB) y almacenamiento en búfer. Para producir audio de alta calidad y deducir el contexto, el modelo necesita un determinado umbral de texto de entrada. Cuanto más texto se envíe en una conexión WebSocket, mejor será la calidad del audio. Si no se alcanza el umbral, el modelo añadirá el texto a un búfer y generará audio cuando el búfer esté lleno.

En términos de latencia, el TTFB es el tiempo que tarda en enviarse el primer byte de audio al cliente. Es importante porque afecta a la latencia percibida del audio. Por ello, quizá quieras controlar el tamaño del búfer para equilibrar calidad y latencia.

Para gestionarlo, puedes usar el parámetro chunk_length_schedule al inicializar la conexión WebSocket o al enviar texto. Este parámetro es un array de enteros que representa el número de caracteres que se enviarán al modelo antes de generar audio. Por ejemplo, si estableces chunk_length_schedule en [120, 160, 250, 290], el modelo generará audio después de que se hayan enviado 120, 160, 250 y 290 caracteres, respectivamente.

Aquí tienes un ejemplo de cómo funciona con los ajustes predeterminados de chunk_length_schedule:

En el diagrama anterior, el audio solo se genera después de enviar el segundo mensaje al servidor. Esto se debe a que el primer mensaje está por debajo del umbral de 120 caracteres, mientras que el segundo mensaje eleva el número total de caracteres por encima del umbral. El tercer mensaje supera el umbral de 160 caracteres, por lo que el audio se genera y devuelve al cliente inmediatamente.

Puedes especificar un valor personalizado para chunk_length_schedule al inicializar la conexión WebSocket o al enviar texto.

await websocket.send(json.dumps({
"text": text,
"generation_config": {
# Generate audio after 50, 120, 160, and 290 characters have been sent
"chunk_length_schedule": [50, 120, 160, 290]
},
"xi_api_key": ELEVENLABS_API_KEY,
}))

Si quieres forzar la devolución inmediata del audio, puedes usar flush: true para vaciar el búfer y forzar la generación de cualquier texto almacenado. Esto puede ser útil, por ejemplo, cuando has llegado al final de un documento y quieres generar audio para la sección final.

Puedes especificarlo para cada mensaje estableciendo flush: true en el mensaje.

await websocket.send(json.dumps({"text": "Generate this audio immediately.", "flush": True}))

Además, al cerrar el websocket se forzará automáticamente la generación de cualquier texto almacenado en el búfer.

Ajustes de voz

Al inicializar las conexiones WebSocket, puedes especificar los ajustes de voz para las generaciones posteriores. Esto te permite controlar la velocidad, la estabilidad y otras características de voz del audio generado.

await websocket.send(json.dumps({
"text": text,
"voice_settings": {"stability": 0.5, "similarity_boost": 0.8, "use_speaker_boost": False},
}))

Puedes sustituirlos para cada mensaje especificando otros voice_settings en el mensaje.

Diccionarios de pronunciación

Puedes utilizar diccionarios de pronunciación para controlar la pronunciación de palabras o frases específicas. Esto puede resultar útil para garantizar que determinadas palabras se pronuncien correctamente o para añadir énfasis a ciertas palabras o frases.

A diferencia de voice_settings y generation_config, los diccionarios de pronunciación deben especificarse en el mensaje “Initialize Connection”. Consulta la referencia de la API para obtener más información.

Al utilizar diccionarios de pronunciación basados en fonemas con WebSockets, debes añadir enable_ssml_parsing=true como parámetro de consulta a la URI del WebSocket. Por ejemplo:

wss://api.elevenlabs.io/v1/text-to-speech/{voice_id}/stream-input?model_id={model_id}&enable_ssml_parsing=true

Recomendaciones

  • Te sugerimos utilizar el ajuste predeterminado de chunk_length_schedule en generation_config.
  • Al desarrollar una aplicación de agente conversacional en tiempo real, recomendamos usar flush: true junto con el texto al final del turno de conversación para garantizar una generación de audio puntual.
  • Si el ajuste predeterminado no ofrece una latencia óptima para tu caso de uso, puedes modificar chunk_length_schedule. Sin embargo, ten en cuenta que reducir la latencia mediante este ajuste puede afectar a la calidad.

Consejos

  • La conexión WebSocket se cerrará automáticamente tras 20 segundos de inactividad. Para mantenerla abierta, puedes enviar un único carácter de espacio " ". Ten en cuenta que esta cadena debe incluir un espacio, ya que enviar una cadena completamente vacía, "", cerrará el WebSocket.
  • Envía una cadena vacía para cerrar la conexión WebSocket después de enviar el último mensaje de texto.
  • Puedes usar alignment para obtener marcas de tiempo a nivel de palabra para cada palabra del texto. Esto puede ser útil para alinear el audio con el texto de un vídeo o para otras aplicaciones que requieran una sincronización precisa. Consulta la referencia de la API para obtener más información.

Siguientes pasos