Route Asterisk AMD audio at answer without waiting on silence
Start audio analysis as soon as the answered channel is available, then route on a returned verdict instead of waiting out a fixed greeting timer.
Separate audio analysis from call control: receive the detection result, then use an asynchronous ESL handler to transfer the live channel.
To divert a FreeSWITCH call on an answering-machine detection (AMD) result, keep two jobs distinct. A media path supplies audio for classification. An Event Socket Library (ESL) connection observes and controls the FreeSWITCH channel. ESL events are not the audio stream, and an ESL listener cannot classify audio it never receives.
AMDY.IO provides AMD for FreeSWITCH and a WebSocket API. Its detection distinguishes live humans, answering machines and carrier false answers (FAS), and starts detection in 1/8 of a second. That is a start time, not a promise that a final verdict will be available at that instant. The adapter between your media path and call controller must use the interface and verdict format configured for your deployment; do not assume a particular event name or JSON field.
Before wiring the socket, decide what each result should do. A human result can transfer to the agent path. A machine result might go to a voicemail workflow or end according to your dialer policy. Treat FAS separately: it describes an answer signal that may not represent an answer by the called party, so route it to a review or failure path rather than counting it as a human.
Use a small, explicit mapping from verdicts to destinations. Reject unknown verdicts and calls whose channel has already ended. Keep the call UUID with the result so that a late response cannot redirect a different call. These checks belong in your application; ESL does not supply the AMD policy.
In outbound socket mode, FreeSWITCH opens a TCP connection to your application when the dialplan reaches the socket application. Your handler completes the ESL handshake, subscribes to events for that channel and remains available to issue commands. Configure the event-socket module and dialplan for your environment, and restrict the socket to trusted hosts. Do not expose an unauthenticated control connection to the public network.
A non-blocking Python handler can read ESL frames without holding up the event loop. ESL messages have headers terminated by a blank line; event bodies, when present, are sized by Content-Length. A minimal frame reader looks like this:
Frame reader: async def read_frame(reader):
headers = {}
while True:
line = await reader.readline()
if line in (b"\n", b"\r\n"): break
key, value = line.decode().split(":", 1)
headers[key] = value.strip()
size = int(headers.get("Content-Length", "0"))
body = await reader.readexactly(size) if size else b""
return headers, body
After accepting the connection, read the initial channel headers, reply with connect\n\n, then subscribe to that channel's events with myevents\n\n. Read and dispatch frames continuously. Handle channel hangup events by discarding any pending verdict state. A real application should also handle command replies and reconnects rather than assuming every socket stays open.
The AMD adapter should deliver its result to the application that owns the ESL session. That can be an asynchronous queue keyed by call UUID. The ESL reader continues handling events while the adapter waits for classification; neither task should block on a synchronous network call. The exact adapter code depends on the selected AMDY.IO interface and its configured message contract, so keep that boundary explicit instead of inventing a vendor event format.
When the application has a valid result and destination, issue a transfer on the channel. For an outbound ESL session, the command can be sent as a message using the ESL execute form:
Transfer command: sendmsg
call-command: execute
execute-app-name: transfer
execute-app-arg: destination XML context
In code, write the complete command to the socket and await writer.drain(); do not wait synchronously for a verdict inside the event-reading loop. Validate the destination against your routing map before formatting the command. The example shows the control step, not a complete production handler: context names, destinations, failure behavior and the media handoff are installation-specific.
Test human, machine and FAS outcomes, plus a call that hangs up before classification finishes. Log the UUID, event timestamps, verdict arrival and chosen route. Compare answer time with transfer time; the advertised 1/8-second detection start does not establish the total time to a usable verdict or agent connection.
Also verify that the audio path actually reaches the AMD service and that the ESL controller still owns the channel when the result arrives. If the controller cannot receive media, fix that path separately. The distinction between acoustic classification and speech transcription is useful background; the trade-off between a direct acoustic verdict and transcribing a greeting affects what work the media adapter needs to do.
The practical pattern is simple: stream audio to detection, pass a normalized verdict to an asynchronous call controller, and let ESL perform the channel action. Keeping those responsibilities separate makes it easier to diagnose missed media, late verdicts and incorrect transfers without confusing an ESL event with the audio evidence behind the decision.
Start audio analysis as soon as the answered channel is available, then route on a returned verdict instead of waiting out a fixed greeting timer.
Pair SIP-proxy routing with call-audio classification, then quarantine suspect trunks only after a measured pattern—not a single false answer.
Combine OpenSIPS routing, Asterisk call handling and AMDY.IO detection—then automate trunk quarantine only after your own policy verifies the pattern.