Хабрахабр

SIP телефон на STM32F7-Discovery

Всем привет.

Тут надо сказать, что та версия была минимальной и соединяла два телефона напрямую без сервера и с передачей голоса лишь в одну сторону. Некоторое время назад мы писали о том как нам удалось запустить SIP телефон на STM32F4-Discovery c 1 Мб ROM и 192 Кб RAM) на базе Embox. Поэтому мы решили запустить более полноценный телефон со звонком через сервер, передачей голоса в обе стороны, но при этом уложиться в как можно меньший размер памяти.

Для телефона было решено выбрать приложение simple_pjsua в составе библиотеки PJSIP. Это минимальное приложение, которое умеет регистрироваться на сервере, принимать и отвечать на звонки. Ниже я сразу приведу описание того как это запустить на STM32F7-Discovery.

Как запускать

  1. Конфигурируем Embox

    make confload-platform/pjsip/stm32f7cube

  2. В файле conf/mods.config задаем нужный SIP аккаунт.


    include platform.pjsip.cmd.simple_pjsua_imported( sip_domain="server", sip_user="username", sip_passwd="password")

    где server — это SIP server (например, sip.linphone.org), username и password — имя пользователя и пароль от аккаунта.

  3. Собираем Embox командой make. Про прошивку платы у нас есть на вики и в статье.
  4. Запускаем в консоли Embox команду “simple_pjsua_imported”


    00:00:12.870 pjsua_acc.c ....SIP outbound status for acc 0 is not active
    00:00:12.884 pjsua_acc.c ....sip:alexk2222@sip.linphone.org: registration success, status=200 (Registration succes
    00:00:12.911 pjsua_acc.c ....Keep-alive timer started for acc 0, destination:91.121.209.194:5060, interval:15s

  5. Наконец, осталось вставить колонки или наушники в аудио выход, а говорить в два маленьких MEMS микрофона рядом с дисплеем. Звоним с линукса через приложение simple_pjsua, pjsua. Ну или можно любое другое типа linphone.

Все это описано на нашем вики.

Как мы к этому пришли

Итак, изначально встал вопрос о выборе аппаратной платформы. Так как было ясно, что STM32F4-Discovery не подойдет по памяти, была выбрана STM32F7-Discovery. У нее 1 Мб флэшка и 256 Кб ОЗУ (+ 64 специальной быстрой памяти, которую мы тоже будем использовать). Тоже не густо для звонков через сервер, но решили попробовать влезть.

Условно для себя задачу разделили на несколько этапов:

  • Запуск PJSIP на QEMU. Это было удобно для отладки, плюс у нас там уже была поддержка кодека AC97.
  • Запись голоса и воспроизведение на QEMU и на STM32.
  • Портирование приложения simple_pjsua из состава PJSIP. Оно позволяет регистрироваться на SIP сервере и звонить.
  • Развернуть свой собственный сервер на базе Asterisk и тестировать на нем, после чего попробовать внешние, такие как sip.linphone.org

Звук в Embox работает через Portaudio, который используется и в PISIP. На QEMU проявились первые проблемы — хорошо проигрывались WAV на 44100 Hz, а вот на 8000 что-то явно шло не так. Оказалось, что дело было в настройке частоты — по умолчанию в аппаратуре она была 44100, и у нас это программно не изменялось.

Звуковой карте можно установить некоторый указатель на кусок памяти, из которого нужно проигрывать или записывать на заранее установленной частоте. Здесь, наверное, стоит немного пояснить как вообще происходит проигрывание звука. Дело в том, что эти буферы нужно успевать заполнять заранее, пока проигрывается предыдущий. После того как буфер заканчивается, вырабатывается прерывание, и выполнение продолжается со следующего буфера. С этой проблемой мы еще столкнемся дальше на STM32F7.

Так как отлаживаться нужно было много, а говорить в микрофон много не хотелось, то нужно было сделать автоматическое проигрывание и запись. Далее, мы арендовали сервер и развернули на нем Asterisk. В PJSIP это делается довольно просто, так как у них есть понятие порт, которым может являться как устройство, так и файл. Для этого мы пропатчили simple_pjsua так, чтобы можно было подсунуть файлы вместо аудиоустройств. Посмотреть код можно в нашем pjsip репозитории. И эти порты можно гибко подключать к другим портам. На сервере Asterisk я завел два аккаунта — для Linux и для Embox. В итоге, схема была следующая. В момент соединения проверяем на сервере Asterisk, что все соединение установлено, и спустя некоторое время должны услышать в Embox звук с Линукса, а в Линуксе сохраняем тот файл, который проигрывается из Embox. Далее на Embox выполняется команда simple_pjsua_imported, Embox регистрируется на сервере, после чего с Линукса звоним на Embox.

Первая проблема — не влазили в 1 Мб ROM без включенной оптимизации компилятора “-Os” по размеру образа. После того как это заработало на QEMU, мы перешли к портированию на STM32F7-Discovery. Далее, патчем отключили поддержку C++, так она нужна только для pjsua, а мы используем simple_pjsua. Поэтому включили “-Os”.

Но сначала нужно было разобраться с записью и воспроизведением голоса. После того как уместили simple_pjsua, решили, что шансы это запустить теперь есть. Выбрали внешнюю память — SDRAM (128 Мб). Вопрос — куда записывать? Вы можете попробовать это сами:

Создаст стерео WAV с частотой 16000 Hz и продолжительностью 10 секунд:


record -r 16000 -c 2 -d 10000 -m C0000000

Проигрываем:


play -m C0000000

Тут возникло две проблемы. Первая с кодеком — используется WM8994, и в нем есть такое понятие как слот, и этих слотов 4. Так вот, по умолчанию, если это не настраивать, то при воспроизведении аудио, проигрывание происходит во всех четырех слотах. Поэтому на частоте 16000 Hz мы получали 8000 Hz, ну а для 8000 Hz воспроизведение просто не работало. Когда выбрали только слоты 0 и 2, то заработало как надо. Еще одной проблемой был аудио интерфейс в STM32Cube, в котором аудио выход работает через SAI (Serial Audio Interface) синхронно с аудио входом (не разбирался в деталях, но получается что они делят общий clock и при инициализации аудио выхода к нему как-то привязывается аудио вход). То есть запустить их по отдельности нельзя, поэтому сделали следующее — всегда работают (в том числе генерируются прерывания) аудио вход и аудио выход. Но когда в системе ничего не проигрывается, то мы просто подсовываем аудио выходу пустой буфер, а когда запускается проигрывание, то по-честному начинаем его заполнять.

Это происходит из-за того, что MEMS микрофоны на STM32F7-Discovery как-то плохо работают на частотах ниже 16000 Hz. Далее столкнулись с тем, что звук при записи голоса был очень тихим. Для этого правда понадобилось добавить программное преобразование одной частоты в другую. Поэтому выставляем 16000 Hz, если даже приходит 8000 Hz.

По нашим подсчетам, pjsip требовал около 190 Кб, а у нас осталось всего около 100 Кб. Далее пришлось увеличить размер кучи, которая располагается в RAM. Тут пришлось задействовать немного внешней памяти — SDRAM (около 128 Кб).

Но звук был ужасный, совсем не такой как на QEMU, ничего нельзя было разобрать. После всех этих правок я увидел первые пакеты между Линуксом и Embox, и услышал звук! Отладка показала, что Embox просто не успевает заполнять/выгружать аудио буферы. Тогда мы задумались в чем может быть дело. Первой мыслью для ускорения была оптимизация компилятора, но она была уже включена в PJSIP. Пока pjsip обрабатывает один фрейм, успевало произойти 2 прерывания о завершении обработки буферов, что слишком много. Но как показала практика, FPU не дало существенного прироста в скорости. Второе — аппаратная плавающая точка, про нее мы рассказывали в статье. В Embox есть разные стратегии планирования, и я включил ту, которая поддерживает приоритеты, и выставил аудио потокам самый максимальный приоритет. Следующим шагом было выставление приоритетов потоков. Это тоже не помогло.

Я провел предварительный анализ того когда и под что simple_pjsua выделяет память. Следующая была идея, что мы работаем с внешней памятью и хорошо бы туда переместить структуры, к которым обращение происходит крайне часто. Далее во время входящего звонка вызывается функция pjsua_call_answer, в которой затем и выделяются буферы для работы с входящими и исходящими фреймами. Оказалось, что из 190 Кб первые 90 Kб выделяются под внутренние нужды PJSIP и к ним обращение происходит не очень часто. И тут мы поступили следующим образом. Это было еще около 100 Кб. Как только звонок — сразу подменяем кучу на другую — в RAM. До момента звонка данные располагаем во внешнюю память. Таким образом, все “горячие” данные были перенесены в более быструю и предсказуемую память.

А затем уже и через другие сервера такие как sip.linphone.org. В итоге все это вместе позволило запустить simple_pjsua и позвонить через свой сервер.

Выводы

В итоге получилось запустить simple_pjsua с передачей голоса в обе стороны через сервер. Проблему с дополнительно потраченными 128 Кб SDRAM можно решить путем использования чуть более мощного Cortex-M7 (например, STM32F769NI с 512 Кб RAM), но при этом мы еще не оставили надежды влезть и в 256 Кб 🙂 Будем рады, если кто-то заинтересуется, а еще лучше — попробует. Все исходники, как обычно, есть в нашем репозитории.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть