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

Всім привіт.

Деякий час тому ми писали про те, як нам вдалося запустити SIP телефон на STM32F4-Discovery з 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")
    

    де сервер - це SIP server (наприклад, sip.linphone.org), ім'я користувача и пароль — Ім'я користувача та пароль від облікового запису.

  3. Збираємо команду Embox зробити. Про прошивку плати у нас є на вики і в статті.
  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:[email protected]: 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. Так як налагоджуватися потрібно було багато, а говорити в мікрофон багато не хотілося, потрібно було зробити автоматичне програвання і запис. Для цього ми пропатчили simple_pjsua так, щоб можна було підсунути файли замість аудіопристроїв. У PJSIP це робиться досить просто, оскільки вони мають поняття порт, яким може бути як пристрій, і файл. І ці порти можна гнучко підключати до інших портів. Переглянути код можна у нашому pjsip репозиторії. У результаті схема була наступна. На сервері Asterisk я завів два облікові записи — для Linux і для Embox. Далі на Embox виконується команда simple_pjsua_imported, Embox реєструється на сервері, після чого з Лінукса дзвонимо на Embox. У момент з'єднання перевіряємо на сервері Asterisk, що все з'єднання встановлено, і через деякий час повинні почути в Embox звук з Лінукса, а в Лінукс зберігаємо той файл, який програється з Embox.

Після того, як це запрацювало на QEMU, ми перейшли до портування STM32F7-Discovery. Перша проблема - не влазили в 1 Мб ROM без увімкненої оптимізації компілятора "-Os" за розміром образу. Тому включили "-Os". Далі, патчем відключили підтримку C + +, так вона потрібна тільки для pjsua, а ми використовуємо simple_pjsua.

Після того, як помістили 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. Для цього правда знадобилося додати програмне перетворення однієї частоти на іншу.

Далі довелося збільшити розмір купи, яка знаходиться в RAM. За нашими підрахунками, pjsip вимагав близько 190 Кб, а в нас залишилося лише близько 100 Кб. Тут довелося залучити трохи зовнішньої пам'яті - SDRAM (близько 128 Кб).

Після всіх цих правок я побачив перші пакети між Лінуксом та Embox, та почув звук! Але звук був жахливий, зовсім не такий, як на QEMU, нічого не можна було розібрати. Тоді ми замислилися у чому може бути справа. Налагодження показало, що Embox просто не встигає заповнювати/вивантажувати буфери аудіо. Поки pjsip обробляє один кадр, встигало відбутися 2 переривання про завершення обробки буферів, що занадто багато. Першою думкою для прискорення була оптимізація компілятора, але вона вже була включена до PJSIP. Друге - апаратна плаваюча точка, про неї ми розповідали в статті. Але, як показала практика, FPU не дало істотного приросту в швидкості. Наступним кроком було виставлення пріоритетів потоків. Embox має різні стратегії планування, і я включив ту, яка підтримує пріоритети, і виставив аудіо потокам наймаксимальніший пріоритет. Це також не допомогло.

Наступна була ідея, що ми працюємо із зовнішньою пам'яттю і добре б туди перемістити структури, до яких звернення відбувається вкрай часто. Я провів попередній аналіз того коли і під що simple_pjsua виділяє пам'ять. Виявилося, що з 190 Кб перші 90 Kб виділяються під внутрішні потреби PJSIP і до них звернення не дуже часто. Далі під час вхідного дзвінка викликається функція pjsua_call_answer, в якій потім і виділяються буфери для роботи з вхідними та вихідними кадрами. Це було близько 100 Кб. І тут ми вчинили так. До моменту дзвінка дані розташовуємо у зовнішню пам'ять. Як тільки дзвінок – відразу підміняємо купу на іншу – в RAM. Таким чином, усі “гарячі” дані були перенесені у швидшу та передбачувану пам'ять.

У результаті все це разом дозволило запустити simple_pjsua та зателефонувати через свій сервер. А потім вже й через інші сервери, такі як sip.linphone.org.

Висновки

У результаті вдалося запустити simple_pjsua з передачею голосу обидві сторони через сервер. Проблему з додатково витраченими 128 Кб SDRAM можна вирішити шляхом використання трохи потужнішого Cortex-M7 (наприклад, STM32F769NI з 512 Кб RAM), але при цьому ми ще не залишили надії влізти і в 256 Кб 🙂 Будемо раді, якщо хтось зацікавиться а ще краще - спробує. Усі вихідники, як завжди, є в нашому репозиторії.

Джерело: habr.com

Додати коментар або відгук