Witajcie wszyscy.
Jakiś czas temu opowiadaliśmy, jak udało nam się uruchomić telefon SIP na STM32F4-Discovery z 1 MB ROM i 192 KB RAM oparty na . Należy zaznaczyć, że ta wersja była minimalna i łączyła dwa telefony bezpośrednio, bez serwera, z przesyłaniem głosu tylko w jedną stronę. Dlatego postanowiliśmy uruchomić bardziej pełnoprawny telefon z połączeniem przez serwer, przesyłaniem głosu w obie strony, przy tym jednak zmieścić się w jak najmniejszym rozmiarze pamięci.

Dla telefonu postanowiliśmy wybrać aplikację simple_pjsua w ramach biblioteki PJSIP. To minimalna aplikacja, która potrafi rejestrować się na serwerze, odbierać i wykonywać połączenia. Poniżej od razu przedstawię opis, jak to uruchomić na STM32F7-Discovery.
Jak uruchomić
- Konfigurujemy Embox
make confload-platform/pjsip/stm32f7cube - W pliku conf/mods.config ustawiamy odpowiednie konto SIP.
include platform.pjsip.cmd.simple_pjsua_imported( sip_domain="serwer", sip_user="nazwa_użytkownika", sip_passwd="hasło")gdzie server — to serwer SIP (na przykład sip.linphone.org), nazwa użytkownika i hasło — nazwa użytkownika i hasło do konta.
- Budujemy Embox poleceniem make. Mamy w tej kwestii dokumentację na i w .
- Uruchamiamy w konsoli Embox polecenie “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: rejestracja zakończona sukcesem, status=200 (Rejestracja zakończona sukcesem) 00:00:12.911 pjsua_acc.c ....Timer keep-alive uruchomiony dla acc 0, cel: 91.121.209.194:5060, interwał: 15s - W końcu została tylko kwestia podłączenia głośników lub słuchawek do wyjścia audio, a mówienie do dwóch małych mikrofonów MEMS blisko wyświetlacza. Dzwonimy z Linuksa przez aplikację simple_pjsua, pjsua. Można też użyć dowolnej innej, jak linphone.
Wszystko to opisaliśmy w naszym .
Jak do tego doszliśmy
Otóż na początku pojawiła się kwestia wyboru platformy sprzętowej. Ponieważ było jasne, że STM32F4-Discovery nie nadaje się z powodu pamięci, wybrano STM32F7-Discovery. Ma ona 1 MB pamięci flash i 256 KB RAM (+ 64 KB specjalnej szybkiej pamięci, którą również będziemy wykorzystywać). To również nie jest wiele do połączeń przez serwer, ale postanowiliśmy spróbować się zmieścić.
Umownie podzieliliśmy zadanie na kilka etapów:
- Uruchomienie PJSIP na QEMU. To było wygodne do debugowania, a dodatkowo mieliśmy tam wsparcie dla kodeka AC97.
- Rejestracja głosu i odtwarzanie na QEMU i na STM32.
- Portowanie aplikacji simple_pjsua z biblioteki PJSIP. Pozwala na rejestrację na serwerze SIP i dzwonienie.
- Uruchomienie własnego serwera opartego na Asterisku i testowanie na nim, po czym spróbować zewnętrznych, takich jak sip.linphone.org
Dźwięk w Embox działa poprzez Portaudio, który jest również wykorzystywany w PISIP. Na QEMU ujawniły się pierwsze problemy — dobrze odtwarzane były pliki WAV przy 44100 Hz, ale przy 8000 Hz coś wyraźnie nie działało. Okazało się, że problem tkwił w ustawieniu częstotliwości — domyślnie w sprzęcie była to 44100, a u nas nie zmieniano tego programowo.
Tutaj warto może wyjaśnić, jak w ogóle odbywa się odtwarzanie dźwięku. Karcie dźwiękowej można ustawić wskaźnik na obszar pamięci, z którego należy odtwarzać lub nagrywać przy wcześniej ustalonej częstotliwości. Po zakończeniu bufora generowane jest przerwanie, a wykonanie kontynuuje się z następnego bufora. Problem polega na tym, że te bufory trzeba wypełniać na czas, podczas gdy odtwarzany jest poprzedni. Z tym problemem jeszcze się zmierzymy później na STM32F7.
Następnie wynajęliśmy serwer i uruchomiliśmy na nim Asterisk. Ponieważ trzeba było dużo debugować, a rozmowy przez mikrofon nie były zbytnio atrakcyjne, postanowiliśmy zrealizować automatyczne odtwarzanie i nagrywanie. W tym celu załataliśmy simple_pjsua, aby możliwe było podsyłanie plików zamiast urządzeń audio. W PJSIP jest to dość proste, ponieważ mają pojęcie portu, którym może być zarówno urządzenie, jak i plik. Te porty można elastycznie podłączać do innych portów. Kod można zobaczyć w naszym pjsip. . W rezultacie, schemat był następujący. Na serwerze Asterisk założyłem dwa konta — dla Linuxa i dla Embox. Następnie na Embox wykonujemy polecenie simple_pjsua_imported, Embox rejestruje się na serwerze, po czym z Linuxa dzwonimy na Embox. W momencie połączenia sprawdzamy na serwerze Asterisk, że całe połączenie zostało ustanowione, a po pewnym czasie powinniśmy usłyszeć w Embox dźwięk z Linuxa, a w Linuxie zapisujemy ten plik, który jest odtwarzany z Embox.
Po tym, jak to zadziałało na QEMU, przeszliśmy do portowania na STM32F7-Discovery. Pierwszy problem — nie mieściliśmy się w 1 MB ROM bez włączonej optymalizacji kompilatora „-Os” ze względu na rozmiar obrazu. Dlatego włączyliśmy „-Os”. Następnie, za pomocą patcha, wyłączyliśmy wsparcie dla C++, jako że jest ono potrzebne tylko dla pjsua, a my używamy simple_pjsua.
Po tym, jak się zmieściliśmy simple_pjsua, postanowiliśmy, że teraz są szanse na uruchomienie. Ale najpierw trzeba było zająć się nagrywaniem i odtwarzaniem głosu. Pytanie — gdzie nagrywać? Wybraliśmy pamięć zewnętrzną — SDRAM (128 MB). Możecie spróbować tego sami:
Stworzy stereo WAV z częstotliwością 16000 Hz i długością 10 sekund:
record -r 16000 -c 2 -d 10000 -m C0000000
Odtwarzamy:
play -m C0000000
Tutaj pojawiły się dwie problemy. Pierwszy dotyczył kodeka — używany jest WM8994 i ma on coś, co nazywa się slotami, a jest ich 4. Domyślnie, jeśli tego nie skonfigurujesz, odtwarzanie dźwięku odbywa się we wszystkich czterech slotach. Dlatego przy częstotliwości 16000 Hz otrzymywaliśmy 8000 Hz, a dla 8000 Hz odtwarzanie po prostu nie działało. Gdy wybraliśmy tylko sloty 0 i 2, wszystko zadziałało jak należy. Kolejnym problemem był interfejs audio w STM32Cube, w którym wyjście audio działa przez SAI (Serial Audio Interface) synchronnie z wejściem audio (nie zagłębiałem się w szczegóły, ale okazuje się, że dzielą wspólny zegar i podczas inicjalizacji wyjścia audio somehow wiąże się to z wejściem audio). To znaczy, nie można ich uruchomić osobno, więc zrobiliśmy tak, że audio wejście i wyjście zawsze działają (w tym generowane są przerwania). Ale kiedy w systemie nic nie jest odtwarzane, po prostu podajemy pusty bufor do wyjścia audio, a kiedy uruchamiamy odtwarzanie, zaczynamy go szczerze wypełniać.
Następnie napotkaliśmy problem, że dźwięk przy nagrywaniu głosu był bardzo cichy. Stało się tak, ponieważ mikrofony MEMS na STM32F7-Discovery jakoś źle działają na częstotliwościach poniżej 16000 Hz. Dlatego ustawiamy 16000 Hz, nawet jeśli dostajemy 8000 Hz. Prawda, że do tego trzeba było dodać programowe przekształcenie jednej częstotliwości w drugą.
Następnie musiałem zwiększyć rozmiar sterty, która znajduje się w RAM. Z naszych obliczeń, pjsip wymagał około 190 KB, a nam zostało tylko około 100 KB. Tutaj musiałem zaangażować nieco pamięci zewnętrznej — SDRAM (około 128 KB).
Po wszystkich tych poprawkach zobaczyłem pierwsze pakiety między Linuksem a Emboxem i usłyszałem dźwięk! Ale dźwięk był okropny, zupełnie inny niż w QEMU, nic nie można było zrozumieć. Wtedy zaczęliśmy się zastanawiać, co mogło być przyczyną. Debugging pokazał, że Embox po prostu nie nadąża z wypełnianiem/wypuszczaniem buforów audio. Podczas gdy pjsip przetwarzał jeden frame, zdążyło się wydarzyć 2 przerwania o zakończeniu przetwarzania buforów, co było zbyt dużo. Pierwszą myślą dla przyspieszenia była optymalizacja kompilatora, ale już była włączona w PJSIP. Drugie — sprzętowa arytmetyka zmiennoprzecinkowa, o której już mówiliśmy. . Jednak, jak pokazała praktyka, FPU nie przyniosło znaczącego wzrostu prędkości. Następnym krokiem było ustalenie priorytetów dla strumieni. W Embox istnieją różne strategie planowania, włączyłem tę, która obsługuje priorytety i nadałem strumieniom audio najwyższy priorytet. To również nie pomogło.
Następną ideą było to, że pracujemy z pamięcią zewnętrzną i dobrze by było przenieść tam struktury, do których dostęp jest bardzo częsty. Przeprowadziłem wstępną analizę, kiedy i na co simple_pjsua przydzielana jest pamięć. Okazało się, że z 190 KB pierwsze 90 KB jest przydzielane na wewnętrzne potrzeby PJSIP i dostęp do nich nie jest zbyt częsty. Następnie podczas przychodzącego połączenia wywoływana jest funkcja pjsua_call_answer, w której przydzielane są buforów do obsługi przychodzących i wychodzących ramek. To było kolejne 100 KB. I tu postąpiliśmy w następujący sposób. Do momentu połączenia dane przechowujemy w pamięci zewnętrznej. Gdy tylko następuje połączenie — natychmiast zamieniamy stertę na inną — w RAM. W ten sposób wszystkie „gorące” dane zostały przeniesione do szybszej i bardziej przewidywalnej pamięci.
W rezultacie wszystko to razem pozwoliło uruchomić simple_pjsua i zadzwonić przez własny serwer. A potem również przez inne serwery takie jak sip.linphone.org.
Wnioski
W efekcie udało się uruchomić simple_pjsua z przesyłem głosu w obie strony przez serwer. Problem z dodatkowymi 128 KB SDRAM można rozwiązać, korzystając z nieco mocniejszego Cortex-M7 (na przykład STM32F769NI z 512 KB RAM), ale nadal nie straciliśmy nadziei na zmieszczenie się w 256 KB 🙂 Będziemy wdzięczni, jeśli ktoś się zainteresuje, a jeszcze lepiej — spróbuje. Jak zwykle, wszystkie źródła są w naszym .
Źródło: habr.com
