1С + asterisk (потоковая передача данных из канала) часть 3. Реал тайм распознавание речи. Часть 1.

26.11.20

Интеграция - Телефония, SIP

Пример реализации потокового распознавания. Версия и релиз технологической платформы не имеет значения (так как 1Cа здесь нет, 1С есть в предыдущих статьях). Продолжение предыдущей статьи. (https://infostart.ru/1c/articles/1022878/).

Задача:

Построить реал тайм диалог с возможностью распознавания.

 

Общая логика решения задачи:

1. Дозвониться

2. Прочитать текст

3. Записать речь в поток

4. Выполнить распознавание речи

5. В зависимости от результата распознавания принять решение либо перейти в п2, либо в п6

6. В зависимости от принятия решения выполнить некие действия в 1С(бизнес логику)

7. Сохранить историю событий asterisk

8. Завершить звонок

 

Технологии:

1. 1С (выборка данных, инициализация вызова, обработка завершения, бизнес логика) 

2. asterisk(16) (ami сервер, agi клиент) (телефония)

3. python(3.6) (web сервер между фронтом и телефонией, ami клиент, agi сервер, web socket сервер)

4. Yandex.Cloud (распознавание речи)

5. С (extension asterisk, asterisk application, web socket клиент)

 

Общий принцип работы:

1) 1С регламентным заданием делает:

    1) Запрашивает у бэка статусы по ранее отправленным задачам

    2) Сохраняет статусы в регистре сведений

    3) Выборку по предварительно сформированным задачам. Из выборки берется телефон для набора, и определяется путь к файлу, который будет в дальнейшем воспроизводится. 1С делает гет запрос в бэк, передавая параметры (телефон, путь к файлу). (фактически создает таск в бэк)

    4) реализацию бизнес логики

2) Бэк:

    1) При старте:

        1) Запускает прослушку событий ами (ami)

        2) Стартует вэб сервер (flask)

        3) Стартует тисипи сервер(TCP) для обработки аги (agi)

        4) Стартует веб сокет сервер (web-socket) для обработки потока с астериска

    2) При получении события по ами делает запись в базу

    3) При получении события по аги делает логику диалога звонка(воспроизведение файлов, распознование с потока, управление фреймами от веб сокет сервера, запуск миксмонитор(запись звонка для прослушки), ложит трубку)

    4) При получении данных по веб сокету, хранит фреймы от астериска

    5) При получении запроса по HTTP парсит параметры, вызывает действие "Originate" с указанием  параметров(телефон, путь к файлу, контекст диалплана, канала вызывающей стороны)

3) Asterisk после успешного поднятия трубки на отвечающей стороне(через макрос):

    1) запускает тисипи поток(канала с передачей голоса) через расширение астериск(вызов application)

    2) "уходит" в аги

 

Отступление: 1Са в этой статье не будет, 1С был в предыдущих. С того времени мало что изменилось 

Часть 1. Исследование

Изначально были попытки реализации через просто буферное чтение файлов записи разговора в аги вида: RECORD FILE {FILENAME} {FORMAT} {ESCAPE_DIGITS} {TIMEOUT} {OFFSET_SAMPLES} {BEEP} {S(количество секунд тишины до завершения)} - это аги команда из мануала по астеру. Проблема заключается в том что аги это блокирующий контекст. То есть пока пишется файл записи разговора НЕЛЬЗЯ:

1. прервать запись(по нормальному имеется ввиду)(может только вызываемая сторона по дтфм)

2. запустить что то на выполнение в диалплане(аги)(запустив рекорд файл(запись), плейбэк(воспроизведение) запустится после завершения рекорд файл)

С этим жить можно было бы( и решить), НО люди могут начать говорить в момент воспроизведения файла\либо наоборот после окна записи(3\5 сек) что составляло задержки и не подходило совсем(в файле часть слов, либо вообще файл пустой)

Так же были попытки использования производных AGI(fast, E, Async), в результате чего было принято решение делать именно стрим(как и почему будет ниже), а не парсить файлы с записью по тайм ауту. Так как проблема оставалась либо блокировка в аги, либо не попадание в окно записи + задержки

Помимо аги комманды RECORD FILE(запись в файл), были попытки использовать аги комманды(application) CHAN SPY, EXTENDED CHAN SPY, APP JACK, AUDIO HOOK (сравнение, обзор эксплуатации и мысли по ним приводить здесь не буду, там еще на статью слов наберется), и прочее с сайта wiki.asterisk.org/wiki/display/AST/Asterisk+16+AGI+Commands, так же были игры с NGINX отдачей побуферно без особого успеха, либо мозгов не хватило, либо поняли что то не так, короче на завелось.

+\- в это же время попалось на глаза https://wiki.asterisk.org/wiki/display/AST/AudioSocket что привело к размышлениям об идеи собрать и использовать модуль https://wiki.asterisk.org/wiki/display/AST/Asterisk+18+Application_AudioSocket так как в у астериска 16 его конечно же не было ни в чистом виде(.с), ни в бинарном(.so)

(*ули нам, кабанам) Касательно AudioSocket, удалось его собрать https://github.com/CyCoreSystems/audiosocket, запустить, даже поток валить начал, + есть обертка для питона https://github.com/NormHarrison/audiosocket_server  но в нем был небольшой баг: в момент работы он грузил проц на 100 процентов, приложение стоящее внимание конечно.

Что было дальше?

Разбор https://github.com/CyCoreSystems/audiosocket/blob/master/asterisk/apps/app_audiosocket.c с целью понять как работает, откуда нагрузка проц, для того чтобы перепилить\напилить свою. Итог разбора был утешающим: 

ответ на первый вопрос если коротко в audiosocket_run блок ast_audiosocket_send_frame.

ответ на второй вопрос там же всё это обернуто в сишный while (1) {...} без прерываний по таймаутам.

Ну собственно всё ясно +\-:

1. интрефейс вызова модуля(load_module, unload_module, audiosocket_run, audiosocket_exec + хидеры) со стороны кор(core) астериска(api application)

2. как вообще снять стрим с астериска

3. что нужно делать дальше(разбить на потоки, так как приложение запускается в контексте аги и в процессе коры (core) астериска, что может сыграть злую шутку(тормоза в голосе, общие проблемы с астериском))

Следующим шагом был поход в вики раздел для разработчиков, а потом и гит хаб астера  поиск по ast_audiosocket_send_frame параллельно начали посещать идеи как разбить на потоки отправку, но до этого дело не дошло. В результате натыкаемся на ast_websocket_write https://asterisk-doxygen.osso.pub/master/api/db/db6/structast__websocket.html (int AST_OPTIONAL_API_NAME(ast_websocket_write)(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t payload_size)). Немного погуглив внезапно находим https://github.com/nadirhamid/asterisk-audiofork смотрим в код, видим ast_pthread_create_detached_background(&thread, NULL, audiofork_thread, audiofork); треды, все дела, судя по коду то что нужно...

Ну в принципе ДА за исключением:

1. NOTE: you will need to use version 7.2.0 or below as there is currently a client side masking issue in res_http_websocket.c - здесь речь идет о не понятно с первого взгляда зачем использовать для ноды модуль сервера 7.2.0. если коротко не вникая в протокол веб сокетов могу сказать что он просто видоизменен, в том плане что в нем нет передачи бита маски со стороны клиента https://asterisk-doxygen.osso.pub/master/api/de/d4c/res__http__websocket_8c.html#a1a6e44db94d8b4e8c06bf456bb0174ad

2. не понятно как гуид в канал установить(я про переменную канала)(для того чтобы понимать какой поток какому звонку относится) в коде есть регистрация менеджера(ami), в мануале описан параметр, но так не получилось завести.

Решение 1 вопроса: пишем себ сокет сервер по реализацию клиента без учета маски https://github.com/dmarenin/asterisk-audiofork/blob/master/websocket_server.py, для сравнения полный протокол https://github.com/Pithikos/python-websocket-server/blob/master/websocket_server/websocket_server.py,

Решение 2 вопроса: добавляем отправку названия канала вида (SIP\xxx) первым сообщением https://github.com/dmarenin/asterisk-audiofork/commit/e1ca8142409e7ad11cdad501c7c5c5cbd68176c1

пример чтения фреймов с астера на веб сокет сервере https://github.com/dmarenin/asterisk-audiofork/blob/master/wss_sample.py

Собираем:

 

Запускаем:
  same => n,Verbose(audio fork was started continuing call)
  same => n,AudioFork(ws://192.168.555.222:8081/,D(out)i(WS_SOCKET_SESSION))

Проверяем:

Немного тестовых примеров:

https://yadi.sk/d/vAEREPOO4064cQ

https://yadi.sk/d/BZrdyE39Ln3Y4Q

https://yadi.sk/d/_L-0--p_7Z7Eqg

В следующей части будет рассмотрена техническая сторона вопроса

ami agi asterisk python web-socket flask extension-asterisk

См. также

Телефония, SIP Платформа 1С v8.3 1С:Управление нашей фирмой 3.0 Россия Управленческий учет Платные (руб)

Модуль интеграции с виртуальной телефонией Новофон для решения 1С:Управление нашей фирмой, редакция 3.0. Обновление релиза в связи с переходом на API 2.0 поставщиком сервиса. Бета-версия 2.0 до окончания перехода поставщиком на новую версию.

12000 руб.

26.02.2024    946    1    0    

2

Разработка внешних компонент Телефония, SIP Программист Платформа 1С v8.3 Конфигурации 1cv8 Россия Платные (руб)

Внешняя компонента выполнена по технологии Native API для 1С 8.х, обеспечивает доступ к программным АТС Asterisk (FreePBX, Elastix) через AMI интерфейс. Через него можно управлять многими функциями Asterisk (определение номеров, перевод звонков, набор телефона и т. д.)

2400 руб.

04.05.2018    46786    122    66    

66

Телефония, SIP Пользователь Платформа 1С v8.3 1С:Бухгалтерия 2.0 1С:Управление торговлей 10 Россия Абонемент ($m)

Данная внешняя обработка позволяет делать звонки из программы 1С, используя сервис сайта zvonok.com.

1 стартмани

18.01.2024    971    1    v3132    0    

3

Телефония, SIP Россия Бесплатно (free)

Делимся опытом разработки системы обзвона абонентов-задолжников.

18.01.2024    1206    slavik27    11    

5

Телефония, SIP Программист Пользователь Платформа 1С v8.3 1С:Управление торговлей 11 Абонемент ($m)

Отображение информации о клиенте из 1С при входящем звонке — настраиваем интеграцию с Softphone.Pro.

1 стартмани

20.07.2022    3343    1    nikolay.surdo    0    

3

WEB-интеграция Телефония, SIP Программист Платформа 1С v8.3 Конфигурации 1cv8 Россия Абонемент ($m)

Внешняя обработка для выгрузки записей разговоров с виртуальной АТС Билайна.

1 стартмани

01.07.2022    2637    3    s_evgen    0    

0
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. AntoShiK86 31 07.10.20 13:30 Сейчас в теме
2. dmarenin 354 12.10.20 07:41 Сейчас в теме
(1)скорее не по, а с астером
3. user1021926 13.02.22 18:02 Сейчас в теме
  File "1.py", line 34, in <module>
    ws_server.set_fn_client_left(self.client_left)
NameError: name 'self' is not defined



не работает питон сампле, пишет что селф не объявлен
4. dmarenin 354 13.02.22 18:07 Сейчас в теме
(3) 1. вы привели не весь код. 2. если селф не определен значит вы не создали объект. 3. проверьте отступы. 4. все работает прекрасно. https://github.com/dmarenin/asterisk-audiofork
5. user1021926 13.02.22 23:36 Сейчас в теме
import sys
from websocket_server import WebsocketServer


def new_client(self, client, server):
    pass

def client_left(self, client, server):
    pass

def message_received(self, client, server, message):
    if message[:3]==b'SIP':
        client['channel'] = message.decode()
        client['frames'] = []
        client['last_offset_read'] = 0

        server.client_channels[client['channel']] = client

        return

    if not client.get('channel') is None:
        client['frames'].append(message)

        #print(message)

        #f = open('audio.raw', 'ab')
        #f.write(message)
        #f.close()



ws_server = WebsocketServer(2700, 'localhost')
ws_server.client_channels = {}
ws_server.set_fn_client_left(self.client_left)
ws_server.set_fn_message_received(self.message_received)
ws_server.set_fn_new_client(self.new_client)
ws_server.run_forever()
Показать


я ничего не придумывал просто скопировал из wss_sample.py как есть, но оно пишет такую ошибку как я ранее писал
6. dmarenin 354 14.02.22 04:39 Сейчас в теме
(5) ...
ws_server.set_fn_client_left(client_left)
ws_server.set_fn_message_received(message_received)
ws_server.set_fn_new_client(new_client)
...
7. user1021926 14.02.22 08:47 Сейчас в теме
Exception happened during processing of request from ('127.0.0.1', 34350)
Traceback (most recent call last):
  File "/usr/lib/python3.6/socketserver.py", line 724, in __init__
    self.handle()
  File "/root/python/websocket_server.py", line 148, in handle
    self.handshake()
  File "/root/python/websocket_server.py", line 251, in handshake
    self.server._new_client_(self)
  File "/root/python/websocket_server.py", line 111, in _new_client_
    self.new_client(client, self)
TypeError: new_client() missing 1 required positional argument: 'server'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.6/socketserver.py", line 654, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python3.6/socketserver.py", line 364, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/root/python/websocket_server.py", line 137, in __init__
    StreamRequestHandler.__init__(self, socket, addr, server)
  File "/usr/lib/python3.6/socketserver.py", line 726, in __init__
    self.finish()
  File "/root/python/websocket_server.py", line 268, in finish
    self.server._client_left_(self)
  File "/root/python/websocket_server.py", line 115, in _client_left_
    self.client_left(client, self)
TypeError: client_left() missing 1 required positional argument: 'server'
Показать


у объявленных методов класса API и вызываемых из класса WebsocketServer не совпадают количество аргументов исправил в 111 поставил в конце на угад TCPServer вроде дальше пошло но застряло на 101 строке
self.message_received(self.handler_to_client(handler), self, msg)

тут тоже количество не совпало с API, в апи 4 а тут 3 не знаю что подставить..
8. dmarenin 354 14.02.22 09:06 Сейчас в теме
(7) вот архив, то что точно работает. дальше давайте сами. websocket_server должен лежать в папке где стартует скрипт - изменен(я про пип, зависимости и версии в архиве)
Прикрепленные файлы:
CallAuto.7z
9. dmarenin 354 14.02.22 09:07 Сейчас в теме
(8) так же в нем пример построения диалога. ведь ваша задача скорее всего в этом.
10. user1857027 07.10.22 18:39 Сейчас в теме
Вы не сталкивались с такими проблемами:
from app_audiofork.c:46:
app_audiofork.c: In function ‘launch_audiofork_thread’:
app_audiofork.c:744:41: error: ‘AST_AUDIOHOOK_SUBSTITUTE_SILENCE’ undeclared (first use in this function)
ast_set_flag(&audiofork->audiohook, AST_AUDIOHOOK_SUBSTITUTE_SILENCE);
^
/usr/src/asterisk/asterisk-16.0.1/include/asterisk/utils.h:74:22: note: in definition of macro ‘ast_set_flag’
((p)->flags |= (flag)); \
^
app_audiofork.c:744:41: note: each undeclared identifier is reported only once for each function it appears in
ast_set_flag(&audiofork->audiohook, AST_AUDIOHOOK_SUBSTITUTE_SILENCE);
^
/usr/src/asterisk/asterisk-16.0.1/include/asterisk/utils.h:74:22: note: in definition of macro ‘ast_set_flag’
((p)->flags |= (flag)); \
^
make[1]: *** [app_audiofork.o] Error 1
make: *** [apps] Error 2


Asterisk 16.0.1
11. dmarenin 354 25.10.22 18:02 Сейчас в теме
(10)нет, завелось как в статье описано. У вас судя по мейк выводу проблема в том что нет функции этой в исходниках астера, рекомендую поднять версию Астера
Оставьте свое сообщение