-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathextensions_pngnpbx_hh.conf
346 lines (318 loc) · 12.6 KB
/
extensions_pngnpbx_hh.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
; extensions_pngnpbx_hh.conf
;
; Dial plan demo for Asterisk for nearly continuous text-to-speech
; as well as basic Speech...() examples to help test your setup.
;
; Feeds call audio from one channel into a new conference,
; and also attaches additional channels to the conference,
; in order to record and/or transcribe the audio in chunks,
; while letting the OG channel carry-on in the dial plan.
;
; When installed with working Speech...() engines, such
; as res_speech_vosk, the words spoken by the OG caller
; are available in the OG channel's SHARED() variables.
;
; Copyright 2024 Penguin PBX Solutions
;
; Licensed under the GPLv3
;
; Sponsored by Helping Hands India NGO
;
; Tested with Asterisk version 20.6.0, and the Vosk module for Asterisk,
; and a very small patch for partial speech results handling.
; DEMO 0
;
; After the beep, try saying a word and/or pressing a button on your phone.
; This is one basic way to do Automatic Speech Recognition (ASR) in Asterisk.
; Good starting point if changing from Background() to SpeechBackground().
; Nothing special here. Helpful for quickly debugging setup eg. does Vosk work?
; Because if this does not work, then nothing else will be fun in this file ;(
;
[pngnpbx-hh-demo-0]
exten = s,1,Answer()
same = n,Wait(1)
;same = n,Set(DENOISE(rx)=on) ; might help detection
;same = n,Set(AGC(rx)=8000) ; might help detection
same = n,SpeechCreate()
same = n,SpeechBackground(beep,5)
same = n,Set(safe_speech_text_0=${FILTER(a-zA-Z0-9 ,${SPEECH_TEXT(0)})})
same = n,Set(safe_speech_score_0=${FILTER(a-zA-Z0-9 ,${SPEECH_SCORE(0)})})
same = n,Set(safe_speech_text_1=${FILTER(a-zA-Z0-9 ,${SPEECH_TEXT(1)})})
same = n,Set(safe_speech_score_1=${FILTER(a-zA-Z0-9 ,${SPEECH_SCORE(1)})})
same = n,SpeechDestroy()
same = n,Playback(ascending-2tone)
same = n,Set(speech_digits=)
same = n,Set(dtmf_digits=${FILTER(0-9,${safe_speech_text_0})})
; Vosk returns digits when DTMF touch tones are used
same = n,GotoIf($[${LEN(${dtmf_digits})}]?${dtmf_digits},1)
; but this current file provides helper context to convert speech to digits
same = n,Gosub(pngnpbx-hh-grammar-digits-en-us,s,1(${safe_speech_text_0}))
same = n,Set(speech_digits=${FILTER(0-9,${GOSUB_RETVAL})})
same = n,GotoIf($[${LEN(${speech_digits})}]?${speech_digits},1)
same = n(words),Playback(you-entered)
same = n,SayAlpha(${safe_speech_text_0})
same = n,Playback(vm-goodbye)
same = n,Hangup()
exten = _X,1,NoOp(one digit)
same = n,Goto(d${EXTEN},1)
exten = _XX,1,NoOp(two digits)
same = n,Goto(d${EXTEN},1)
exten = _XXX,1,NoOp(three digits)
same = n,Goto(d${EXTEN},1)
exten = _XXX.,1,NoOp(four or more digits)
same = n,Goto(d${EXTEN},1)
exten = _d.,1,NoOp()
same = n,Playback(you-entered&digits)
same = n,GotoIf($[${LEN(${speech_digits})}]?digits)
same = n,SayAlpha(DTMF)
same = n(digits),SayDigits(${EXTEN:1})
same = n,Playback(vm-goodbye)
same = n,Hangup()
; DEMO 1
;
; After the beep, say a word, or press a button;
; Hear tones, then say another word, or press a button.
; If nothing works, then make sure you have Vosk running.
; If it works a bit but not quite right, then try talking s-l-o-w-e-r.
;
[pngnpbx-hh-demo-1]
exten = s,1,Answer()
same = n,Gosub(pngnpbx-hh-init,s,1) ; Minimal Required Part
same = n,NoOp(modify the sample glue below per your requirements)
same = n,Set(palace=${GOSUB_RETVAL})
same = n,Background(silence/1)
same = n,Background(phonetic/e_p)
same = n,Background(beep)
same = n,Verbose("SAY A WORD")
same = n,Set(talking1=${SHARED(hh_word)})
same = n,While($[!${LEN(${talking1})}])
same = n,Background(silence/1)
same = n,Set(talking1=${SHARED(hh_word)})
same = n,EndWhile()
same = n,Background(ascending-2tone)
same = n,Verbose("SAY ANOTHER WORD")
same = n,Set(talking2=${SHARED(hh_word)})
same = n,While($["${talking1}" == "${talking2}"])
same = n,Background(silence/1)
same = n,Set(talking2=${SHARED(hh_word)})
same = n,EndWhile()
same = n,Background(ascending-2tone)
same = n,NoOp(Or you could dial something instead...)
;same = n,Dial(PJSIP/7004) ; or go to an IVR, etc.
same = n,NoOp(You can stop the ASR Conference Palace early...)
same = n,Gosub(pngnpbx-hh-hup,${palace},1)
same = n,Verbose("YOU SAID '${talking1}' THEN '${talking2}'")
same = n,NoOp(...or it will stop when the OG caller hangs up.)
same = n,Playback(you-entered)
same = n,SayAlpha(${talking1})
same = n,Playback(and)
same = n,SayAlpha(${talking2})
same = n,Playback(vm-goodbye)
same = n,Hangup()
; DEMO 2
;
; Plays music until you say "penguin",
; then Allison says something silly.
;
[pngnpbx-hh-demo-2]
exten = s,1,Answer()
same = n,Gosub(pngnpbx-hh-init,s,1(talk))
same = n(moh),MusicOnHold(default)
same = n,Hangup()
same = n(talk),NoOp(what did you say?)
same = n,Set(talking=${SHARED(hh_word)})
same = n,GotoIf($["${talking}"!="penguin"]?moh)
same = n,Playback(why-no-answer-mystery) ; LOL
same = n,Goto(moh)
; INIT
;
; Main place you should Gosub() to from elsewhere in your dialplan.
; Cascades signals to downstream contexts found further below,
; including hangup handlers and several new helper calls (via async originate.)
;
; Ordered Parameters:
; ARG1 - return priority name (or number) that the originating channel
; is redirected to when talking is detected - this lets you
; speech-enable any application the caller may be in eg. MusicOnHold()
;
; Returns:
; palace - The name of the ConfBridge() - useful to shut down ASR
; before the OG call is hung up eg. you are done with transcription.
;
[pngnpbx-hh-init]
exten = s,1,NoOp(Initializing ASR)
same = n,Set(LOCAL(ogpritalk)=${ARG1})
same = n,Set(LOCAL(ogctx)=${STACK_PEEK(1,c)})
same = n,Set(LOCAL(ogext)=${STACK_PEEK(1,e)})
same = n,Set(LOCAL(caller)=${CHANNEL(name)})
same = n,Set(LOCAL(opts1)=caller="${caller}")
same = n,Set(LOCAL(opts2)=ogext=${ogext}^ogctx=${ogctx}^ogpritalk=${ogpritalk})
same = n,Set(LOCAL(palace)=p${MD5(${RAND}${UNIQUEID}):24})
same = n,Set(LOCAL(body)=Local/${palace}@pngnpbx-hh-bridge)
same = n,Set(LOCAL(ear)=pngnpbx-hh-listen)
same = n,Set(LOCAL(hand)=pngnpbx-hh-record)
same = n,Set(LOCAL(brain)=pngnpbx-hh-analyze)
same = n,Set(CHANNEL(hangup_handler_push)=pngnpbx-hh-hup,${palace},1)
; Asynchronously spool three calls to do the work.
; 1. ear - listens - uses ChanSpy() to feed OG caller audio into ConfBridge()
same = n,Originate(${body},exten,${ear},${palace},1,2,av(${opts1}))
same = n,NoOp(ear originate_status=${ORIGINATE_STATUS})
; 2. hand - records - saves ConfBridge() audio using Record()
; LOOK OUT 8-) a lot of recordings are made, so you might want to comment out!
same = n,Originate(${body},exten,${hand},${palace},1,2,a)
same = n,NoOp(hand originate_status=${ORIGINATE_STATUS})
; 3. brain - analyzes - lets Speech...() convert speech-to-text into SHARED()
same = n,Originate(${body},exten,${brain},${palace},1,2,av(${opts1}^${opts2}))
same = n,NoOp(brain originate_status=${ORIGINATE_STATUS})
same = n,DumpChan(5)
same = n,Return(${palace})
; HUP
;
; Hangup handler for shutting down the ASR Conference Palace.
;
[pngnpbx-hh-hup]
exten = _p.,1,DumpChan(5)
same = n,Set(LOCAL(palace)=${FILTER(pabcdef0123456789,${EXTEN})})
same = n,Set(LOCAL(palace_count)=${CONFBRIDGE_INFO(parties,${palace})})
same = n,GotoIf($[!${LEN(${palace_count})}]?out)
same = n,GotoIf($[!${palace_count}]?out)
same = n,ConfKick(${palace})
same = n(out),Return()
; BRIDGE
;
; Sets reasonable defaults and enters the Conference Palace.
; Called from several places.
;
[pngnpbx-hh-bridge]
exten = _p.,1,DumpChan(5)
same = n,Set(palace=${FILTER(pabcdef0123456789,${EXTEN})})
same = n,Answer()
same = n,Set(CONFBRIDGE(user,announce_join_leave)=no)
same = n,Set(CONFBRIDGE(user,announce_only_user)=no)
same = n,Set(CONFBRIDGE(user,announce_user_count)=no)
same = n,Set(CONFBRIDGE(user,announce_user_count_all)=no)
same = n,Set(CONFBRIDGE(user,startmuted)=no)
same = n,Set(CONFBRIDGE(user,quiet)=yes)
same = n,ConfBridge(${palace})
same = n,NoOp(confbridge_result=${CONFBRIDGE_RESULT})
same = n,Hangup()
; LISTEN
;
; The "ear". Listens to the caller. Sends their audio to the Conference Palace.
;
[pngnpbx-hh-listen]
exten = _p.,1,DumpChan(5)
same = n,Set(palace=${FILTER(pabcdef0123456789,${EXTEN})})
same = n,Answer()
same = n,ChanSpy(${caller:1:-1},EoqsSu)
same = n,NoOp(spy_channel=${SPY_CHANNEL})
same = n,Hangup()
; RECORD
;
; The "hand". Repeatedly record Conference Palace audio into many wav files.
;
[pngnpbx-hh-record]
exten = _p.,1,DumpChan(5)
same = n,Set(palace=${FILTER(pabcdef0123456789,${EXTEN})})
same = n,Set(rectimeout=2)
; Yes the recording will end early when the OG hangs up,
; but recording in four second chunks means not much is lost,
; and these recordings are really only useful for debugging.
same = n,Set(reclength=4)
same = n,Set(recbase=/var/spool/asterisk/recording/pngnpbx-hh-${palace})
same = n,Set(recformat=wav)
same = n,Answer()
same = n,While(1)
same = n,Record(${recbase}-${EPOCH}.${recformat},${rectimeout},${reclength},q)
same = n,NoOp(recorded_file=${RECORDED_FILE})
same = n,NoOp(record_status=${RECORD_STATUS})
same = n,EndWhile()
same = n,Hangup()
; ANALYZE
;
; The "brain". Repeatedly does ASR on Conference Palace audio.
; Sets variables with speech detection result words and scores.
; Optionally redirects the original caller channel when they are done talking.
;
[pngnpbx-hh-analyze]
exten = _p.,1,DumpChan(5)
same = n,Set(palace=${FILTER(pabcdef0123456789,${EXTEN})})
same = n,Set(speechengine=)
same = n,Set(speechtimeout=3)
same = n,Answer()
same = n,While(1)
same = n,SpeechCreate(${speechengine})
; The SpeechBackground() app plays this file, which makes the speech
; detection less than continuous; so probably should modify Speech...() apps
; to handle a timeout without a file, or some thing like that (TODO...)
;same = n,SpeechStart() ; do not use this, needs upstream improvements
same = n,SpeechBackground(ascending-2tone,${speechtimeout})
same = n,Set(safe_speech_text_0=${FILTER(a-zA-Z0-9 ,${SPEECH_TEXT(0)})})
same = n,Set(safe_speech_score_0=${FILTER(a-zA-Z0-9 ,${SPEECH_SCORE(0)})})
; Current res_speech_vosk only returns primary result with score of 100
; but maybe your speech engine is different...
same = n,Set(safe_speech_text_1=${FILTER(a-zA-Z0-9 ,${SPEECH_TEXT(1)})})
same = n,Set(safe_speech_score_1=${FILTER(a-zA-Z0-9 ,${SPEECH_SCORE(1)})})
same = n,SpeechDestroy()
same = n,GotoIf($[!${LEN(${safe_speech_text_0})}]?endloop)
; Can not do AstDB stuff here unless live_dangerously is set in asterisk.conf
same = n,Set(SHARED(hh_last,${caller:1:-1})=${EPOCH})
same = n,Set(SHARED(hh_word,${caller:1:-1})=${safe_speech_text_0})
same = n,Set(SHARED(hh_score,${caller:1:-1})=${safe_speech_score_0})
same = n,GotoIf($[!${LEN(${ogpritalk})}]?endloop)
same = n,ChannelRedirect(${caller},${ogctx},${ogext},${ogpritalk})
same = n(endloop),EndWhile()
same = n,Hangup()
; GRAMMAR DIGITS EN US
;
; Current res_speech_vosk does not support grammars.
; Use this in a Gosub() to help out with detection of spoken numbers 0-20.
;
; Ordered Parameters:
; ARG1 - word string to inspect if it is a spoken number
;
; Returns:
; out - either the number as digit(s), or blank if NaN
;
[pngnpbx-hh-grammar-digits-en-us]
exten = s,1,DumpChan(5)
same = n,Set(LOCAL(in)=${ARG1})
same = n,Set(LOCAL(out)=)
same = n,GotoIf($["${in}"==""]?i,1)
same = n,GotoIf($["${in}"=="zero"]?0,1)
same = n,GotoIf($["${in}"=="one"]?1,1)
same = n,GotoIf($["${in}"=="two"]?2,1)
same = n,GotoIf($["${in}"=="to"]?2,1)
same = n,GotoIf($["${in}"=="too"]?2,1)
same = n,GotoIf($["${in}"=="three"]?3,1)
same = n,GotoIf($["${in}"=="tree"]?3,1)
same = n,GotoIf($["${in}"=="thee"]?3,1)
same = n,GotoIf($["${in}"=="the"]?3,1)
same = n,GotoIf($["${in}"=="four"]?4,1)
same = n,GotoIf($["${in}"=="for"]?4,1)
same = n,GotoIf($["${in}"=="or"]?4,1)
same = n,GotoIf($["${in}"=="five"]?5,1)
same = n,GotoIf($["${in}"=="six"]?6,1)
same = n,GotoIf($["${in}"=="seven"]?7,1)
same = n,GotoIf($["${in}"=="eight"]?8,1)
same = n,GotoIf($["${in}"=="ate"]?8,1)
same = n,GotoIf($["${in}"=="nine"]?9,1)
same = n,GotoIf($["${in}"=="ten"]?10,1)
same = n,GotoIf($["${in}"=="eleven"]?11,1)
same = n,GotoIf($["${in}"=="twelve"]?12,1)
same = n,GotoIf($["${in}"=="thirteen"]?13,1)
same = n,GotoIf($["${in}"=="fourteen"]?14,1)
same = n,GotoIf($["${in}"=="fifteen"]?15,1)
same = n,GotoIf($["${in}"=="sixteen"]?16,1)
same = n,GotoIf($["${in}"=="seventeen"]?17,1)
same = n,GotoIf($["${in}"=="eightteen"]?18,1)
same = n,GotoIf($["${in}"=="nineteen"]?19,1)
same = n,GotoIf($["${in}"=="twenty"]?20,1)
same = n,Goto(i,1)
exten = i,1,Set(out=)
same = n,Return(${out})
exten = _X,1,Set(out=${EXTEN})
same = n,Return(${out})
exten = _XX,1,Set(out=${EXTEN})
same = n,Return(${out})
; end of extensions_pngnpbx_hh.conf