Skip to content

TTS synthesis service

Responsibilities and startup

The TTS server is a Flask HTTP service that synthesizes audio and timing metadata. ROS speech orchestration is a separate layer: it calls synthesis, schedules visemes and gestures, and selects where audio plays. Calling synthesis directly does not itself publish a robot speech command.

The current supporting source branch is sdk3. Use its actual server.py contract; older examples in the repository README do not describe this handler's URL shape.

TTS_CONFIG_FILE must name an existing YAML file at import/startup; otherwise startup raises RuntimeError("TTS config file is not found"). That file supplies viseme mapping configuration. --voice_paths loads Python modules containing a voices mapping. A vendor may map directly to a TTSBase implementation or to a dictionary keyed by voice. A running server with no loaded voices can answer ping but cannot synthesize.

Setting Default or meaning
--host localhost; container entrypoints can override it
--port 10001; current Python 3 service deployment may override to 10002
--tts-output-dir $HR_DIR/ttsserver, with HR_DIR defaulting to ~/.hr
--voice_paths One or more plugin directories; required operational input for voice discovery
TTS_CONFIG_FILE Required viseme/configuration YAML path
HR_SINGING_DIR Optional singing assets used during setup

Provider credentials and licenses are inputs to installed voice plugins. Do not infer provider availability from the built image alone. Keep voice files and licenses outside the documentation site.

GET /ping

No parameters. Returns JSON with a stable application-level success code:

{"response":{"code":0,"message":"pong"}}
export TTS_URL='http://TTS_HOST:TTS_PORT'
curl --fail-with-body "$TTS_URL/ping"

The handler adds Access-Control-Allow-Origin: *. This is a liveness probe, not a test of voices, provider credentials, synthesis or downstream playback.

GET /

The vendor is a path segment, not a query parameter. Supply voice and text as query parameters. format defaults to wav; provider implementations determine supported behavior. Other query values are forwarded to the voice implementation, except text, vmap, and vparam, which are removed from the forwarded parameter dictionary. voice and format remain in that dictionary.

curl --fail-with-body --get "$TTS_URL/azure" \
  --data-urlencode 'voice=CONFIGURED_VOICE' \
  --data-urlencode 'text=Hello from the robot.' \
  --data-urlencode 'format=wav'

The example selects a provider only if its plugin and voice are installed. It is not a claim that a particular cloud voice is provisioned.

Response field inside response Meaning
error Empty on normal success; application failure message otherwise
phonemes Provider phoneme timing data
visemes Mapped visemes when a mapping exists
markers, words, nodes Provider timing, text markers, and structured synthesis nodes
text, id Synthesis text and identifier
format Requested format value
duration Duration reported by synthesis data
data Base64-encoded bytes read from the generated output file; may be empty

The top-level response is {"response": {...}}, not raw WAV bytes. The handler deletes the temporary output file after reading it. A valid synthesis response can lack visemes when a vendor mapping is unavailable. Check error and audio content rather than relying solely on HTTP 200.

Decode a previously saved response locally:

import base64
import json
from pathlib import Path
payload = json.loads(Path("tts-response.json").read_text())["response"]
if payload.get("error"):
    raise RuntimeError(payload["error"])
Path("speech.wav").write_bytes(base64.b64decode(payload["data"], validate=True))

Error and side-effect behavior

  • Unknown vendor/voice: response.error contains Can't get api <vendor>:<voice>.
  • Provider exception: the synthesis call is caught and its message is returned through error.
  • Empty provider result: No TTS data unless a more specific error exists.
  • Missing text or voice is not validated by a typed request model; failures depend on lookup and provider behavior.
  • Some later file/marker errors are raised outside the synthesis exception handler and can produce HTTP 500.
  • A text marker naming an audio file can replace the generated output with an existing server-side file. Treat this interface as privileged; do not expose it publicly just because the documentation is public.
  • Synthesis can incur provider cost and create/delete files. No idempotency-key or server cancellation endpoint is implemented in these two handlers. A timed-out client cannot assume synthesis was canceled.

Visemes and troubleshooting

The source mapping includes Azure, CereProc, Acapela, Polly, ElevenLabs variants and singing. Different ElevenLabs modes use different phoneme-to-viseme mapping families. Verify that the selected plugin and mapping names agree.

For failures, check startup config first, loaded voice modules second, then provider credentials/network/license state. If ping succeeds but speech fails, inspect application error and server logs. If audio succeeds but the mouth does not move, inspect the ROS TTS/animation pipeline rather than retrying synthesis blindly.

Evidence: ttsserver/ttsserver/server.py, ttsbase.py, and head-ros-nodes/docker-compose.ros2.yml. Route declarations preserve exact inspected handlers. No live provider synthesis was performed for this guide.