Why is my Asterisk TTS/AGI script not playing the TTS audio but capturing the microphone input instead?

I am trying to set up a real-time text-to-speech (TTS) and speech-to-text (STT) interaction in Asterisk using Python. When an extension (e.g., 1001) calls another extension (e.g., 1002), the system should answer the call, play a preset TTS prompt (“Hi, thanks for calling, what can I do for you?”), and then allow for a bidirectional conversation using TTS and STT.

I’ve added the following dial plan in /etc/asterisk/extensions_custom.conf:

[custom-live-tts-stt]
exten => 1002,1,Answer()
 same => n,AGI(live_tts_stt.py)
 same => n,Hangup()  ; End the call after AGI

Here’s my Python script (live_tts_stt.py):

#!/usr/bin/env python3

import os
import sys
import speech_recognition as sr
from gtts import gTTS
from pydub import AudioSegment
from pydub.playback import play
import openai

# Set up OpenAI API key
openai.api_key = 'your-openai-api-key'

# Initialize speech recognizer
recognizer = sr.Recognizer()

def record_audio():
    with sr.Microphone() as source:
        print("Listening...")
        audio = recognizer.listen(source)
    return audio

def speech_to_text(audio):
    try:
        text = recognizer.recognize_google(audio)
        print(f"Recognized: {text}")
        return text
    except sr.UnknownValueError:
        print("Could not understand audio")
        return ""
    except sr.RequestError as e:
        print(f"Could not request results; {e}")
        return ""

def generate_response(prompt):
    response = openai.Completion.create(
        engine="text-davinci-003",
        prompt=prompt,
        max_tokens=150
    )
    return response.choices[0].text.strip()

def text_to_speech(text):
    tts = gTTS(text=text, lang='en')
    tts.save("/var/lib/asterisk/agi-bin/response.mp3")
    sound = AudioSegment.from_mp3("/var/lib/asterisk/agi-bin/response.mp3")
    sound.export("/var/lib/asterisk/agi-bin/response.wav", format="wav")

def main():
    # Answer the call
    print("ANSWER")
    sys.stdout.flush()

    while True:
        audio = record_audio()
        user_text = speech_to_text(audio)

        if user_text.lower() in ["exit", "quit", "bye"]:
            break

        response_text = generate_response(user_text)
        text_to_speech(response_text)

        print("STREAM FILE /var/lib/asterisk/agi-bin/response \"\"")
        sys.stdout.flush()

    print("HANGUP")
    sys.stdout.flush()

if __name__ == "__main__":
    main()

Issue: When I call from extension 1001 to 1002, instead of hearing the TTS prompt, the system seems to pick up and transmit live microphone input (i.e., human voice from the environment) rather than playing the generated TTS audio. The intended TTS response is not played back to the caller. ** What I’ve Tried:**

Verified that the AGI script is being executed.
Checked the audio file paths and confirmed the TTS audio files are being generated and saved as expected.
Ensured that the Asterisk server has access to the Python script and required libraries.

Question: Why is the TTS audio not playing to the caller, and how can I ensure the system plays the TTS response instead of capturing the live microphone input?

Any help or guidance would be greatly appreciated!

You should actually show the Asterisk console output including the AGI debug using “agi set debug on”.

Connected to Asterisk 18.16.0 currently running on freepbx (pid = 2164)
– Remote UNIX connection
== Endpoint 1001 is now Unreachable
– Contact 1001/sip:1001@192.168.0.134:44081;x-reg=42AEE6C5555BC8D5 is now Unreachable. RTT: 0.000 msec
freepbxCLI> agi set debug on
AGI Debugging Enabled
freepbx
CLI>
freepbxCLI>
freepbx
CLI>
freepbxCLI>
freepbx
CLI>
freepbx*CLI>
== Endpoint 1001 is now Reachable
– Contact 1001/sip:1001@192.168.0.134:44081;x-reg=42AEE6C5555BC8D5 is now Reachable. RTT: 639.909 msec
– Added contact ‘sip:1002@192.168.0.188:48963;x-reg=60832A9E2F106C50’ to AOR ‘1002’ with expiration of 600 seconds
== Endpoint 1002 is now Reachable
– Contact 1002/sip:1002@192.168.0.188:48963;x-reg=60832A9E2F106C50 is now Reachable. RTT: 2155.927 msec
[2024-08-23 08:58:19] WARNING[28858]: res_pjsip_pubsub.c:3394 pubsub_on_rx_publish_request: No registered publish handler for event presence from 1002
[2024-08-23 08:58:19] WARNING[28858]: res_pjsip_pubsub.c:3394 pubsub_on_rx_publish_request: No registered publish handler for event presence from 1002
[2024-08-23 08:58:19] WARNING[28858]: res_pjsip_pubsub.c:3394 pubsub_on_rx_publish_request: No registered publish handler for event presence from 1002
== Endpoint 1001 is now Unreachable
– Contact 1001/sip:1001@192.168.0.134:44081;x-reg=42AEE6C5555BC8D5 is now Unreachable. RTT: 0.000 msec
– Attempted to remove non-existent contact ‘sip:1001@192.168.0.134:47904;x-reg=42AEE6C5555BC8D5’ from AOR ‘1001’ by request
– Attempted to remove non-existent contact ‘sip:1001@192.168.0.134:47904;x-reg=DEAE50F8E3DCDB15;x-ast-orig-host=192.168.1.6:42899’ from AOR ‘1001’ by request
– Attempted to remove non-existent contact ‘sip:1001@192.168.0.134:47904;x-reg=E3460AE8E87EDC48;x-ast-orig-host=192.168.1.6:54095’ from AOR ‘1001’ by request
– Added contact ‘sip:1001@192.168.0.134:47904;x-reg=B37043E1C17F166E’ to AOR ‘1001’ with expiration of 600 seconds
– Removed contact ‘sip:1001@192.168.0.134:44081;x-reg=42AEE6C5555BC8D5’ from AOR ‘1001’ due to remove existing
== Contact 1001/sip:1001@192.168.0.134:44081;x-reg=42AEE6C5555BC8D5 has been deleted
== Endpoint 1001 is now Reachable
– Contact 1001/sip:1001@192.168.0.134:47904;x-reg=B37043E1C17F166E is now Reachable. RTT: 54.985 msec
[2024-08-23 08:59:10] WARNING[28791]: res_pjsip_pubsub.c:3394 pubsub_on_rx_publish_request: No registered publish handler for event presence from 1001
[2024-08-23 08:59:21] WARNING[28791]: res_pjsip_pubsub.c:3394 pubsub_on_rx_publish_request: No registered publish handler for event presence from 1002
== Using SIP RTP Audio TOS bits 184
== Using SIP RTP Audio TOS bits 184 in TCLASS field.
== Using SIP RTP Audio CoS mark 5
– Executing [1002@from-internal:1] Answer(“PJSIP/1002-00000021”, “”) in new stack
– Executing [1002@from-internal:2] Set(“PJSIP/1002-00000021”, “VOLUME(TX)=15”) in new stack
– Executing [1002@from-internal:3] NoOp(“PJSIP/1002-00000021”, “Started voice assistant”) in new stack
– Executing [1002@from-internal:4] AGI(“PJSIP/1002-00000021”, “sr.py”) in new stack
– Launched AGI Script /var/lib/asterisk/agi-bin/sr.py
<PJSIP/1002-00000021>AGI Tx >> agi_request: sr.py
<PJSIP/1002-00000021>AGI Tx >> agi_channel: PJSIP/1002-00000021
<PJSIP/1002-00000021>AGI Tx >> agi_language: en_GB
<PJSIP/1002-00000021>AGI Tx >> agi_type: PJSIP
<PJSIP/1002-00000021>AGI Tx >> agi_uniqueid: 1724403562.33
<PJSIP/1002-00000021>AGI Tx >> agi_version: 18.16.0
<PJSIP/1002-00000021>AGI Tx >> agi_callerid: 1002
<PJSIP/1002-00000021>AGI Tx >> agi_calleridname: jain
<PJSIP/1002-00000021>AGI Tx >> agi_callingpres: 0
<PJSIP/1002-00000021>AGI Tx >> agi_callingani2: 0
<PJSIP/1002-00000021>AGI Tx >> agi_callington: 0
<PJSIP/1002-00000021>AGI Tx >> agi_callingtns: 0
<PJSIP/1002-00000021>AGI Tx >> agi_dnid: 1002
<PJSIP/1002-00000021>AGI Tx >> agi_rdnis: unknown
<PJSIP/1002-00000021>AGI Tx >> agi_context: from-internal
<PJSIP/1002-00000021>AGI Tx >> agi_extension: 1002
<PJSIP/1002-00000021>AGI Tx >> agi_priority: 4
<PJSIP/1002-00000021>AGI Tx >> agi_enhanced: 0.0
<PJSIP/1002-00000021>AGI Tx >> agi_accountcode:
<PJSIP/1002-00000021>AGI Tx >> agi_threadid: 139912078825216
<PJSIP/1002-00000021>AGI Tx >>
– <PJSIP/1002-00000021>AGI Script sr.py completed, returning 0
– Executing [1002@from-internal:5] GotoIf(“PJSIP/1002-00000021”, “1?endvoice”) in new stack
– Goto (from-internal,1002,9)
– Executing [1002@from-internal:9] Hangup(“PJSIP/1002-00000021”, “”) in new stack
== Spawn extension (from-internal, 1002, 9) exited non-zero on ‘PJSIP/1002-00000021’
– Executing [h@from-internal:1] Macro(“PJSIP/1002-00000021”, “hangupcall”) in new stack
– Executing [s@macro-hangupcall:1] Gosub(“PJSIP/1002-00000021”, “app-missedcall-hangup,s,1()”) in new stack
– Executing [s@app-missedcall-hangup:1] NoOp(“PJSIP/1002-00000021”, “Callee: s”) in new stack
– Executing [s@app-missedcall-hangup:2] NoOp(“PJSIP/1002-00000021”, "Caller: ") in new stack
– Executing [s@app-missedcall-hangup:3] AGI(“PJSIP/1002-00000021”, “agi://127.0.0.1/missedcallnotify.php,s,0,PJSIP/1002-00000021,”) in new stack
AGI Tx >> agi_network: yes
AGI Tx >> agi_network_script: missedcallnotify.php
<PJSIP/1002-00000021>AGI Tx >> agi_request: agi://127.0.0.1/missedcallnotify.php
<PJSIP/1002-00000021>AGI Tx >> agi_channel: PJSIP/1002-00000021
<PJSIP/1002-00000021>AGI Tx >> agi_language: en_GB
<PJSIP/1002-00000021>AGI Tx >> agi_type: PJSIP
<PJSIP/1002-00000021>AGI Tx >> agi_uniqueid: 1724403562.33
<PJSIP/1002-00000021>AGI Tx >> agi_version: 18.16.0
<PJSIP/1002-00000021>AGI Tx >> agi_callerid: 1002
<PJSIP/1002-00000021>AGI Tx >> agi_calleridname: jain
<PJSIP/1002-00000021>AGI Tx >> agi_callingpres: 0
<PJSIP/1002-00000021>AGI Tx >> agi_callingani2: 0
<PJSIP/1002-00000021>AGI Tx >> agi_callington: 0
<PJSIP/1002-00000021>AGI Tx >> agi_callingtns: 0
<PJSIP/1002-00000021>AGI Tx >> agi_dnid: 1002
<PJSIP/1002-00000021>AGI Tx >> agi_rdnis: unknown
<PJSIP/1002-00000021>AGI Tx >> agi_context: app-missedcall-hangup
<PJSIP/1002-00000021>AGI Tx >> agi_extension: s
<PJSIP/1002-00000021>AGI Tx >> agi_priority: 3
<PJSIP/1002-00000021>AGI Tx >> agi_enhanced: 0.0
<PJSIP/1002-00000021>AGI Tx >> agi_accountcode:
<PJSIP/1002-00000021>AGI Tx >> agi_threadid: 139912078825216
<PJSIP/1002-00000021>AGI Tx >> agi_arg_1:
<PJSIP/1002-00000021>AGI Tx >> agi_arg_2:
<PJSIP/1002-00000021>AGI Tx >> agi_arg_3: s
<PJSIP/1002-00000021>AGI Tx >> agi_arg_4: 0
<PJSIP/1002-00000021>AGI Tx >> agi_arg_5:
<PJSIP/1002-00000021>AGI Tx >> agi_arg_6: PJSIP/1002-00000021
<PJSIP/1002-00000021>AGI Tx >> agi_arg_7:
<PJSIP/1002-00000021>AGI Tx >> agi_arg_8:
<PJSIP/1002-00000021>AGI Tx >>
<PJSIP/1002-00000021>AGI Rx << VERBOSE “As a minimum this script requires extension or ‘s’ as argument” 3
– agi://127.0.0.1/missedcallnotify.php,s,0,PJSIP/1002-00000021,: As a minimum this script requires extension or ‘s’ as argument
<PJSIP/1002-00000021>AGI Tx >> 200 result=1
– <PJSIP/1002-00000021>AGI Script agi://127.0.0.1/missedcallnotify.php completed, returning 0
<PJSIP/1002-00000021>AGI Tx >> HANGUP
– Executing [s@app-missedcall-hangup:4] Return(“PJSIP/1002-00000021”, “”) in new stack
– Executing [s@macro-hangupcall:2] GotoIf(“PJSIP/1002-00000021”, “1?theend”) in new stack
– Goto (macro-hangupcall,s,4)
– Executing [s@macro-hangupcall:4] ExecIf(“PJSIP/1002-00000021”, “0?Set(CDR(recordingfile)=)”) in new stack
– Executing [s@macro-hangupcall:5] Hangup(“PJSIP/1002-00000021”, “”) in new stack
== Spawn extension (macro-hangupcall, s, 5) exited non-zero on ‘PJSIP/1002-00000021’ in macro ‘hangupcall’
== Spawn extension (from-internal, h, 1) exited non-zero on ‘PJSIP/1002-00000021’
[2024-08-23 08:59:23] WARNING[28791]: res_pjsip_pubsub.c:3394 pubsub_on_rx_publish_request: No registered publish handler for event presence from 1002
[2024-08-23 08:59:24] WARNING[28791]: res_pjsip_pubsub.c:3394 pubsub_on_rx_publish_request: No registered publish handler for event presence from 1002
– Removed contact ‘sip:1001@192.168.0.134:47904;x-reg=B37043E1C17F166E’ from AOR ‘1001’ due to expiration
– Removed contact ‘sip:1002@192.168.0.188:48963;x-reg=60832A9E2F106C50’ from AOR ‘1002’ due to expiration
== Contact 1001/sip:1001@192.168.0.134:47904;x-reg=B37043E1C17F166E has been deleted
== Endpoint 1001 is now Unreachable
== Contact 1002/sip:1002@192.168.0.188:48963;x-reg=60832A9E2F106C50 has been deleted

According to the output, it’s not executing what you originally provided 'nor is it doing anything with audio from what I can tell. It’s answering the call, executing “sr.py” which doesn’t do anything from an AGI perspective, and then hangs up the call.

Your dialplan is presumably incorrect or not what you expect it to be. It is executing “1002” in the “from-internal” context.

Additionally I believe you are using FreePBX, so you would need to determine the best way to fit in your custom stuff into the FreePBX dialplan.

But I’m login through ssh root user and trying to modify the custom extensions, now what should I do? I’m getting confused.

I can’t really answer that, someone else may be able to. I don’t know what you’ve already done to FreePBX or the best way to put your own dialplan logic in.

I just want to know I have dial plan

[custom-live-tts-stt]
exten => 1002,1,Answer()
 same => n,AGI(live_tts_stt.py)
 same => n,Hangup()

where should I put this ? in /etc/asterisk/extensions_custom.conf or /etc/asterisk/extensions.conf?

If its extensions.conf then should I use this header ‘[custom-live-tts-stt]’ or any other header that usually triggers?

I don’t know FreePBX dialplan or how to inject your own dialplan, so I can’t answer that. You could try asking or searching/looking on the FreePBX forum at https://community.freepbx.org/

Sure. Thank You.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.