Настройка уведомления о записи разговора для исходящих и входящих вызовов в Asterisk
В прошлой статье мы перевели соответствия внешних и внутренних номеров на АТС Asterisk в базу данных MySQL. В данном материале мы рассмотрим пример добавления уведомления о записи разговоров для входящей и исходящей связи. Причём уведомление может быть любым, ведь по сути мы добавляем воспроизведение произвольного аудиофайла перед соединением с клиентом. Это очень востребованная функция, так как если разговор записывается, то клиент, по закону, должен быть уведомлён об этом.
Итак, сценарий работы у нас будет следующий – для исходящей связи:
В базе данных мы проставляем для каждого внутреннего номера признак проигрывать или не проигрывать уведомление о записи разговора. При исходящем вызове в базу данных передаётся внутренний номер абонента станции (в примере ниже 650), который совершает исходящий вызов, в ответ возвращается значение которое определят проигрывать или не проигрывать аудиофайл вызываемому абоненту.
Для входящей связи – при входящем вызове на городской номер АТС, вызываемый городской номер (в примере 78121234567) передаётся в базу данных, откуда возвращается значение, на основании которого АТС определяет проигрывать или не проигрывать аудиофайл уведомления о записи разговора.
Причём, мы рассмотрим два случая: первый – проигрывание уведомления о записи разговора после ответа АТС – состояния “сonnected” и старта тарификации вызова, второй – проигрывание уведомления о записи разговора до ответа АТС и состояния “connected”, соответственно до тарификации. Таким образом, абонент не будет платить за вызов если абонент АТС не ответил.
Теперь рассмотрим техническую реализацию описанных выше сценариев.
Исходящая связь
Чтобы проиграть аудиофайл /var/lib/asterisk/sounds/ivr/razgovor_record
в приложении Dial
используем параметр A(имя_аудиофайла)
. После соединения указанный в скобках аудифайл будет проигран.
Но нам необходимо управлять через базу данных, для исходящих вызовов, с каких внутренних номеров аудиофайл уведомления о записи разговора нужно проигрывать, а для каких не нужно.
Для этого добавим новый столбец в существующей таблице outbound
.
Добавим этот новый столбец после столбца callerid
с именем notification_enabled
, значения в столбце могут принимать следующие значения:
1 – Проиграть аудиофайл уведомления
0 – Не проигрывать аудиофайл уведомления
Добавим столбец в notification_enabled
таблицу outbound
, далее приведён пример запроса в СУБД MySQL.
mysql> use astcdrdb; mysql> ALTER TABLE `outbound` ADD `notification_enabled` INT( 1 ) NOT NULL DEFAULT '0' AFTER `callerid`;
Теперь таблица outbound
будет выглядеть следующим образом:
mysql> select * from outbound where internal=650; +-----+----------+-------------+----------------------+-------+ | id | internal | callerid | notification_enabled | Notes | +-----+----------+-------------+----------------------+-------+ | 210 | 650 | 78121234567 | 0 | | +-----+----------+-------------+----------------------+-------+ 1 row in set (0.00 sec)
Добавим функцию, которая будет проверять значение столбца notification_enabled
в файл func_odbc.conf
.
[CHECK_NOTIF_OUT_ENABLE] dsn=asterisk readsql=SELECT notification_enabled FROM outbound where internal='${ARG1}'
В консоли Asterisk применим функцию:
aster*CLI> module reload func_odbc.so
В выводе консоли Asterisk увидим:
== Registered custom function 'ODBC_CHECK_NOTIF_OUT_ENABLE'
Теперь модифицируем дайлплан, файл extensions.conf.
Чтобы проиграть аудиофайл /var/lib/asterisk/sounds/ivr/razgovor_record
в приложении Dial
используем параметр A(имя_аудиофайла)
. После соединение указанный в параметре аудиофайл будет проигран.
Начнём с контекста [mg-out]
. Исходный вид:
exten => _9[78][123456780]XXXXXXXXX,2,Set(__CALLER=${CALLERID(ANI)}) exten => _9[78][123456780]XXXXXXXXX,3,Set(__CALLED=7${EXTEN:2}) exten => _9[78][123456780]XXXXXXXXX,4,Set(__UID=${UNIQUEID}) exten => _9[78][123456780]XXXXXXXXX,5,Dial(SIP/RTU/7${EXTEN:2},,tTM(startrec))
Модифицируем контекст, новые строки выделены жирным текстом:
exten => _9[78][123456780]XXXXXXXXX,2,Set(__CALLER=${CALLERID(ANI)}) exten => _9[78][123456780]XXXXXXXXX,3,Set(__CALLED=7${EXTEN:2}) exten => _9[78][123456780]XXXXXXXXX,4,Set(__UID=${UNIQUEID}) exten => _9[78][123456780]XXXXXXXXX,5,NoOp(${ODBC_CHECK_NOTIF_OUT_ENABLE(${CALLER})}) exten => _9[78][123456780]XXXXXXXXX,6,Set(NOTIFICATION=${IF($["${ODBC_CHECK_NOTIF_OUT_ENABLE(${CALLER})}" = "1"]?/var/lib/asterisk/sounds/ivr/razgovor_record)}) exten => _9[78][123456780]XXXXXXXXX,7,Dial(SIP/RTU/7${EXTEN:2},,tTM(startrec)A(${NOTIFICATION}))
Далее, перечитываем дайлплан командой из консоли Asterisk: dialplan reload
.
При вызове, если notification_enabled=0
, отладочный вывод в консоли Asterisk будет следующим:
-- Executing [981116275136@mn:1] Set("SIP/650-00003bb2", "CALLERID(num)=78121234567") in new stack -- Executing [981116275136@mn:2] Set("SIP/650-00003bb2", "__CALLER=650") in new stack -- Executing [981116275136@mn:3] Set("SIP/650-00003bb2", "__CALLED=71116275136") in new stack -- Executing [981116275136@mn:4] Set("SIP/650-00003bb2", "__UID=1490019065.32064") in new stack -- Executing [981116275136@mn:5] NoOp("SIP/650-00003bb2", "0") in new stack -- Executing [981116275136@mn:6] Set("SIP/650-00003bb2", "NOTIFICATION=") in new stack -- Executing [981116275136@mn:7] Dial("SIP/650-00003bb2", "SIP/RTU/71116275136,,tTM(startrec)A()") in new stack
При вызове, если notification_enabled=1
:
== Using SIP RTP CoS mark 5 -- Executing [981116275136@mn:1] Set("SIP/650-00003b70", "CALLERID(num)=78121234567") in new stack -- Executing [981116275136@mn:2] Set("SIP/650-00003b70", "__CALLER=650") in new stack -- Executing [981116275136@mn:3] Set("SIP/650-00003b70", "__CALLED=71116275136") in new stack -- Executing [981116275136@mn:4] Set("SIP/650-00003b70", "__UID=1490018964.31913") in new stack -- Executing [981116275136@mn:5] NoOp("SIP/650-00003b70", "1") in new stack -- Executing [981116275136@mn:6] Set("SIP/650-00003b70", "NOTIFICATION=/var/lib/asterisk/sounds/ivr/razgovor_record") in new stack -- Executing [981116275136@mn:7] Dial("SIP/650-00003b70", "SIP/RTU/71116275136,,tTM(startrec)A(/var/lib/asterisk/sounds/ivr/razgovor_record)") in new stack
Исходящая связь
В предыдущем материале посвященном Asterisk мы переводили входящую связь через БД, MySQL и запрашивали параметры входящего вызова из БД, существующие строки в дайлплане:
same => n,NoOp(GET FORWARD ring_timeout,forward_after_enable,forward_after_numer,timeout_ring_after == ${ODBC_GET_FORWARD_AFTER_HG(${EXTEN})}) same => n,Set(ARRAY(RING_TIMEOUT,FORWARD_AFTER_ENABLE,FORWARD_AFTER_NUMBER,TIMEOUT_RING_AFTER)=${ODBC_GET_FORWARD_AFTER_HG(${EXTEN})})
В данные строки нужно добавить получение ещё одного параметра notification_enabled
из таблицы inbound
, который будет указывать на то, нужно ли проигрывать уведомление или нет.
Добавим новый столбец запросом в mysql:
mysql> use astcdrdb; mysql> ALTER TABLE `inbound` ADD `notification_enabled` INT( 1 ) NULL DEFAULT '0' AFTER `timeout_ring_after`
Модифицируем функцию в ODBC_GET_FORWARD_AFTER_HG
в файле func_odbc.conf
, то что добавлено выделено жирным шрифтом:
[GET_FORWARD_AFTER_HG] dsn=asterisk readsql=SELECT ring_timeout,forward_after_enable,forward_after_numer,timeout_ring_after, notification_enabled from inbound where did='${ARG1}'
Затем строки в extensions.conf:
same => n,NoOp(GET FORWARD ring_timeout,forward_after_enable,forward_after_numer,timeout_ring_after, notification_enabled == ${ODBC_GET_FORWARD_AFTER_HG(${EXTEN})}) same => n,Set(ARRAY(RING_TIMEOUT,FORWARD_AFTER_ENABLE,FORWARD_AFTER_NUMBER,TIMEOUT_RING_AFTER,NOTIFICATION_ENABLED)=${ODBC_GET_FORWARD_AFTER_HG(${EXTEN})})
Перечитаем файл с функциями odbc:
aster*CLI> module reload func_odbc.so Module 'func_odbc.so' reloaded successfully. -- Reloading module 'func_odbc.so' (ODBC lookups) == Parsing '/etc/asterisk/func_odbc.conf': Found … == Registered custom function 'ODBC_GET_FORWARD_AFTER_HG' …
Далее, переходим в контекст [HG-NEW]
. Закомментируем первую строку и добавляю NoOp
, также две строки выделенные жирным цветом. Здесь проверяется разрешено ли уведомление – значение NOTIFICATION_ENABLED
равно 1. Если 0, то переменная NOTIFICATION
принимает значение skip
, в опции приложения Playback
из переменой NOTIFICATION
добавляется данный ключ, который указывает что если канал не в ответном состоянии, то приложение Playback
не будет проигрывать указанный файл /var/lib/asterisk/sounds/ivr/razgovor_record
. Первой строкой мы закомментировали приложение Answer
, которое переводит канал в ответное состояние Connected в самом начале контекста. Поэтому эта комбинация будет работать.
Получится вот так:
;exten => _X.,1, Answer exten => _X.,1,NoOp same => n,Set(_MONITOR_FILENAME=IN-${EXTEN}-${STRFTIME(${EPOCH},,%Y_%m_%d[%H_%M])}-${CALLERID(num)}-${EXTEN}-${UNIQUEID}) same => n, NoOp(!!!!CALLED=${CALLED}) same => n, Set(H1=${IF($["${m1}" != ""]?SIP/${m1})}) same => n, Set(H2=${IF($["${m2}" != ""]?&SIP/${m2})}) same => n, Set(H3=${IF($["${m3}" != ""]?&SIP/${m3})}) same => n, Set(H4=${IF($["${m4}" != ""]?&SIP/${m4})}) same => n, Set(H5=${IF($["${m5}" != ""]?&SIP/${m5})}) same => n, Set(H6=${IF($["${m6}" != ""]?&SIP/${m6})}) same => n, Set(H7=${IF($["${m7}" != ""]?&SIP/${m7})}) same => n, Set(H8=${IF($["${m8}" != ""]?&SIP/${m8})}) same => n, Set(H9=${IF($["${m9}" != ""]?&SIP/${m9})}) same => n, Set(H11=${IF($["${m11}" != ""]?&SIP/${m11})}) same => n, Set(H12=${IF($["${m12}" != ""]?&SIP/${m12})}) same => n, Set(H13=${IF($["${m13}" != ""]?&SIP/${m13})}) same => n, Set(H14=${IF($["${m14}" != ""]?&SIP/${m14})}) same => n, Set(H15=${IF($["${m15}" != ""]?&SIP/${m15})}) same => n,Set(NOTIFICATION=${IF($["${NOTIFICATION_ENABLED}" = "1"]?:skip)}) same => n,Playback(/var/lib/asterisk/sounds/ivr/razgovor_record,${NOTIFICATION}) same => n,Dial(${H1}${H2}${H3}${H4}${H5}${H6}${H7}${H8}${H9}${H10}${H11}${H12}${H13}${H14}${H15},${RING_TIMEOUT},TtrM(monext)) ...
Если переменная NOTIFICATION
принимает значение пусто(“”), то Playback
посылает 200OK с SDP (Connect) , переводит канал в ответное состояние и проигрывает файл /var/lib/asterisk/sounds/ivr/razgovor_record
.
Таким образом аудиофайл будет воспроизводиться вне зависимости поддерживает ли операторское оборудование early media.
Ниже SIP-трейс обмена сообщениями:
Capturing on eth0 2.329733 192.168.103.45 -> 192.168.126.210 SIP/SDP 1235 Request: INVITE sip:78121234567@192.168.126.210;user=phone, with session description 2.332353 192.168.126.210 -> 192.168.103.45 SIP 631 Status: 100 Trying 2.365530 192.168.126.210 -> 192.168.103.45 SIP/SDP 913 Status: 200 OK, with session description 2.368216 192.168.103.45 -> 192.168.126.210 SIP 502 Request: ACK sip:78121234567@192.168.126.210:5060 <проигрывается уведомление, состояние ответа, тарификация вызова началась> <Далее вызов на внутреннего абонента> 6.840698 192.168.126.210 -> 192.168.85.148 SIP/SDP 955 Request: INVITE sip:350@192.168.85.148:64141;rinstance=6372aa4716dea7bf, with session description 6.961023 192.168.85.148 -> 192.168.126.210 SIP 462 Status: 180 Ringing 10.469290 192.168.85.148 -> 192.168.126.210 SIP/SDP 841 Status: 200 OK, with session description 10.469761 192.168.126.210 -> 192.168.85.148 SIP 505 Request: ACK sip:350@192.168.85.148:64141;rinstance=6372aa4716dea7bf 11.479193 192.168.85.148 -> 192.168.126.210 SIP 578 Request: BYE sip:78121234567@192.168.126.210:5060 11.480282 192.168.126.210 -> 192.168.85.148 SIP 563 Status: 200 OK 11.481937 192.168.126.210 -> 192.168.103.45 SIP 574 Request: BYE sip:78121234567@192.168.103.45:5063;user=phone 11.484585 192.168.103.45 -> 192.168.126.210 SIP 503 Status: 200 OK
Если встречный оператор поддерживает режим early media или отправку вызывающей стороне аудио в голосовом канале, то cтроку с приложением Playback
можно модифицировать вот так:
same => n,Set(NOTIFICATION=${IF($["${NOTIFICATION_ENABLED}" = "1"]?noanswer:skip)})
То есть, добавить ключ noanswer
, вместо “”(пусто), тогда Playback
будет проигрывать файл в предответном состоянии, то есть до состояния Connect:
same => n,Playback(/var/lib/asterisk/sounds/razgovor_zapis1,${NOTIFICATION})
В sip.conf нужно
Выставить в общих настройках(секция [general]
) параметр
prematuremedia=no
SIP-трейсировка будет следующий:
0.000000 192.168.103.45 -> 192.168.126.210 SIP/SDP 1235 Request: INVITE sip:78121234567@192.168.126.210;user=phone, with session description 0.000815 192.168.126.210 -> 192.168.103.45 SIP 631 Status: 100 Trying 0.031701 192.168.126.210 -> 192.168.103.45 SIP/SDP 929 Status: 183 Session Progress, with session description 0.031775 192.168.126.210 -> 192.168.103.45 RTP 214 PT=ITU-T G.711 PCMU, SSRC=0x6439ADF6, Seq=43650, Time=160, Mark 0.032872 192.168.103.45 -> 192.168.126.210 RTCP 122 Sender Report Source description 0.032922 192.168.103.45 -> 192.168.126.210 SIP 640 Request: OPTIONS sip:78121234567@192.168.126.210;user=phone 0.033428 192.168.126.210 -> 192.168.103.45 SIP 668 Status: 200 OK 0.051935 192.168.126.210 -> 192.168.103.45 RTP 214 PT=ITU-T G.711 PCMU, SSRC=0x6439ADF6, Seq=43651, Time=320 0.071893 192.168.126.210 -> 192.168.103.45 RTP 214 PT=ITU-T G.711 PCMU, SSRC=0x6439ADF6, Seq=43652, Time=480 0.091958 192.168.126.210 -> 192.168.103.45 RTP 214 PT=ITU-T G.711 PCMU, SSRC=0x6439ADF6, Seq=43653, Time=640 <проговаривание аудиофайла> … 4.438201 192.168.126.210 -> 192.168.103.45 SIP 597 Status: 180 Ringing <КПВ вызывающему абоненту>
Вариант с поддержкой early media будет выглядеть вот так:
;exten => _X.,1, Answer exten => _X.,1, NoOp same => n,Set(_MONITOR_FILENAME=IN-${EXTEN}-${STRFTIME(${EPOCH},,%Y_%m_%d[%H_%M])}-${CALLERID(num)}-${EXTEN}-${UNIQUEID}) same => n, NoOp(!!!!CALLED=${CALLED}) same => n, Set(H1=${IF($["${m1}" != ""]?SIP/${m1})}) same => n, Set(H2=${IF($["${m2}" != ""]?&SIP/${m2})}) same => n, Set(H3=${IF($["${m3}" != ""]?&SIP/${m3})}) same => n, Set(H4=${IF($["${m4}" != ""]?&SIP/${m4})}) same => n, Set(H5=${IF($["${m5}" != ""]?&SIP/${m5})}) same => n, Set(H6=${IF($["${m6}" != ""]?&SIP/${m6})}) same => n, Set(H7=${IF($["${m7}" != ""]?&SIP/${m7})}) same => n, Set(H8=${IF($["${m8}" != ""]?&SIP/${m8})}) same => n, Set(H9=${IF($["${m9}" != ""]?&SIP/${m9})}) same => n, Set(H11=${IF($["${m11}" != ""]?&SIP/${m11})}) same => n, Set(H12=${IF($["${m12}" != ""]?&SIP/${m12})}) same => n, Set(H13=${IF($["${m13}" != ""]?&SIP/${m13})}) same => n, Set(H14=${IF($["${m14}" != ""]?&SIP/${m14})}) same => n, Set(H15=${IF($["${m15}" != ""]?&SIP/${m15})}) same => n,Set(NOTIFICATION=${IF($["${NOTIFICATION_ENABLED}" = "1"]?noanswer:skip)}) same => n,Playback(/var/lib/asterisk/sounds/ivr/razgovor_record,${NOTIFICATION}) same => n,Dial(${H1}${H2}${H3}${H4}${H5}${H6}${H7}${H8}${H9}${H10}${H11}${H12}${H13}${H14}${H15},${RING_TIMEOUT},TtrM(monext)) same => n,GotoIf($["${FORWARD_AFTER_ENABLE}" != "1"]?end) same => n,GotoIf($[${REGEX("@" ${FORWARD_AFTER_NUMBER})}]?nochangecid) same => n,Set(CALLERID(num)=${IF($["${REGEX("^7812.......$" ${EXTEN})}"]?${EXTEN}:78120001122)}) same => n(nochangecid),Dial(${FORWARD_AFTER_NUMBER},${TIMEOUT_RING_AFTER},TtrM(monext)) same => n,Goto(CHECK_BUSY_FORWARD,${EXTEN},1) same => n(end),Hangup
Старое оборудование, которое установлено у операторов связи оможет «криво» поддерживать или не поддерживать early media вовсе. Например, по SIP-дампу в RTP трафике в плеере wireshark уведомление на участке между АТС и оператором слышно, а у абоненента в сети ТфОП тишина. В таком случае нужно сообщение connect и следует использовать первый вариант.
Таким образом мы добавили воспроизведение аудиофайла, в частности уведомление о записи разговоара, которое всегда проигрывается только одной стороне — клиенту, а не обоим сторонам вызова. Причём включение и отключение уведомления регулируется путём установки параметра в базе данных MySQL. Поэтому сделать интерфейс для абонентов, где они самостоятельно устанавливать этот параметр не составит труда. Это также можно реализовать через служебные комбинации или старкоды.
Похожие материалы: