Skip to main content

AMD Worker

The AMD Worker is a Java/Vert.x 5 microservice that performs real-time Answering Machine Detection (AMD). It receives live audio streams from Cloudonix via WebSocket, analyzes audio for voicemail beep tones, and posts detection results to the Laravel backend.

Purpose​

The worker handles:

  • Receiving WebSocket audio streams from Cloudonix
  • Decoding mu-law audio to PCM and resampling to 16 kHz
  • Detecting voicemail beeps with ML and energy-based detectors
  • Posting results to Laravel's /api/voice/amd-action endpoint
  • Closing the WebSocket after a result or timeout

When It Is Triggered​

The AMD worker is triggered when:

  1. An outbound auto-dialer campaign has AMD enabled
  2. A call is answered
  3. Laravel returns CXML with a <Start><Stream url="wss://.../ws/amd/detect"> verb
  4. Cloudonix opens a WebSocket to the AMD worker and begins streaming audio

It is also used for any other call flow that wraps CXML with the AMD stream verb.

Architecture​

Build and Run​

The worker starts automatically with the full OPBX stack:

docker compose up -d

Local Build​

cd amd-worker

# Compile
mvn compile

# Run tests
mvn test

# Build shaded JAR
mvn package -DskipTests -B

# Run locally
java -jar target/amd-worker-1.0.0.jar

Docker Build​

cd amd-worker
docker build -t amd-worker:latest .
docker run -p 8082:8082 -p 8083:8083 \
-e AMD_WORKER_API_TOKEN=your-token \
-e AMD_ACTION_CALLBACK_URL=http://host.docker.internal/api/voice/amd-action \
amd-worker:latest

Environment Variables​

VariableDefaultRequiredDescription
AMD_WEBSOCKET_PORT8082NoWebSocket listener port
AMD_HTTP_PORT8083NoHealth/metrics HTTP port
AMD_MODEL_PATH./models/beep_detector.onnxNoONNX model file path
AMD_MAX_CONCURRENT_STREAMS100NoMax simultaneous streams
AMD_DEFAULT_TIMEOUT_SECONDS30NoDetection timeout per stream
AMD_DETECTORSbeep_ml,tone_energyNoEnabled detectors
AMD_LOG_LEVELinfoNoSLF4J log level
AMD_DUMP_AUDIOfalseNoDump streams to WAV for debugging
AMD_DUMP_AUDIO_PATH/tmp/amd-dumpsNoDump directory
AMD_WORKER_API_TOKEN—YesBearer token for callback auth
AMD_ACTION_CALLBACK_URLhttp://nginx/api/voice/amd-actionNoLaravel callback URL
danger

AMD_WORKER_API_TOKEN must match the value configured in OPBX (AMD_WORKER_API_TOKEN in .env). The callback uses constant-time comparison to prevent timing attacks.

Detection Pipeline​

  1. Cloudonix sends a connected event
  2. Cloudonix sends a start event with custom parameters (actions)
  3. The worker stores action_human, action_voicemail, and action_unknown
  4. Cloudonix sends media events every 20 ms with Base64 mu-law audio
  5. The worker decodes to PCM16 and resamples to 16 kHz
  6. Energy VAD segments speech
  7. After ~11.5 seconds, a rolling buffer tone scan checks for beeps
  8. The detection pipeline resolves to voicemail, human, or unknown
  9. The worker POSTs the result to /api/voice/amd-action
  10. Laravel updates the session profile and executes the configured action

Action Options​

Actions are passed via <Parameter> elements inside the CXML <Stream> verb:

<Stream url="wss://.../ws/amd/detect" track="outbound">
<Parameter name="action_voicemail" value="HANGUP"/>
<Parameter name="action_human" value="CONTINUE"/>
<Parameter name="action_unknown" value="CONTINUE"/>
</Stream>
Action ValueBehavior
URL (https://...)Switch the call to the given voice application URL
HANGUPDisconnect the session
CONTINUEClose the WebSocket and take no further action

Default behavior:

  • Voicemail → HANGUP
  • Human or Unknown → CONTINUE

Health Endpoint​

curl http://localhost:8083/health

Returns JSON with status, model load state, active streams, detection counts, and uptime.

Troubleshooting​

IssueSolution
AMD results not appearingVerify AMD_WORKER_API_TOKEN matches OPBX; check docker compose logs -f amd-worker
High false-positive rateReview dumped audio with OfflineToneTest and tune detector parameters
Model fails to loadEnsure AMD_MODEL_PATH points to a valid ONNX file; the worker falls back to energy detection
Streams rejectedCheck concurrent stream count against AMD_MAX_CONCURRENT_STREAMS