Dzisiaj przedstawię najprostszą wersję DIY kamery internetowej opartej o płytkę M1S Dock. Pokażę jak można skompilować demo przesyłania obrazu JPG z kamery przez TCP do serwera w Pythonie oraz przeanalizuję jego działanie, ze szczególnym naciskiem na komunikację pomiędzy dwoma rdzeniami BL808. Następnie przerobię je tak, by działało w trybie serwera HTTP, czyli by można było podejrzeć obraz z kamery wchodząc na prostą stronkę internetową hostowaną na samym M1S Dock.
Powiązany temat:
Pierwsze starcie z modułem SiPEED M1S DOCK - AI+IoT RTOS_Linux All-Round Module
Wstęp
Zacząć trzeba od tego, że BL808 zawiera w sobie dwa osobne procesory o architekturze RISC, jeden 32-bitowy, a drugi aż 64-bitowy, których szczegółowe parametry prezentuję poniżej:
Widzimy tutaj, że jeden rdzeń odpowiada multimedia, a drugi za komunikację bezprzewodową:
W SDK M1s_BL808 panuje następujące nazewnictwo
- c906 - to rdzeń D0 (multimedia)
- e907 - to rdzeń M0 (komunikacja)
Jego znajomość ułatwi nam poruszanie się po SDK od M1S.
Kompilacja przykładów i wgrywanie
Przykłady udało mi się skompilować na Ubuntu. W oficjalnej dokumentacji m1s jest naprawdę szczegółowa instrukcja opisująca cały ten proces krok po kroku, nie ma sensu jej tutaj przepisywać:
https://wiki.sipeed.com/hardware/en/maix/m1s/other/start.html#SDK-Compile
Pod c906 mamy tam całą gamę przykładów, m. in:
- hello_world - najprostszy program wyświetlający komunikat przez UART
- gpio_demo - demonstracja operacji na GPIO
- lvgl_demo - przykłady i benchmark operacji graficznych
- image_processing_demo - przykład przetwarzania obrazów
- pwm_demo - generowanie sygnału PWM
- camera_streaming_through_wifi - streamowanie obrazu z kamerki do serwera przez TCP (serwer napisany jest w Pythonie, klientem jest BL808)
Pod e907 jest jeden wsad, nazwany po prostu firmware.
Spróbujmy uruchomić c906/camera_streaming_through_wifi . Kompilujemy oba wsady, czyli c906/camera_streaming_through_wifi oraz z e908/firmware.
Wgrywamy oba przez UART, a dokładniej przez BLDevCube:
https://wiki.sipeed.com/hardware/en/maix/m1s/other/start.html#SDK-Compile
W BLDevCube należy ustawić:
- tablicę partycji (z SDK M1S)
- boot2 (najnowszy boot2 z BLDevCube, wersja debug)
- firmware - to jest wsad na rdzeń e907
- d0fw - to jest wsad na rdzeń c906
Dodatkowo trzeba zresetować płytkę trzymając przycisk BOOT, ale wszystko jest w zalinkowanej dokumentacji.
U mnie wgrywanie nie chciało działać na jednej z maszyn, nie wiem czemu, na drugim laptopie ruszyło, a oba są z Windows 10.
Teraz można by już odpalić program, ale najpierw przeanalizujmy jego działanie...
Przykład kamerki internetowej - wysyłanie obrazów JPG do serwera w Pythonie
Zatem otwórzmy kod źródłowy programu wysyłającego obraz z kamery. Ten program uruchamia na BL808 klienta TCP, który wysyła obrazy JPG do serwera napisanego w Pythonie.
Kod: C / C++
Hasło WiFi i IP serwera należy zmienić przed kompilacją.
Zaskoczeni? Tu naprawdę nie ma dużo kodu. Cała magia powiązana jest z m1s_xram_wifi_init, czyli systemem komunikacji przez XRAM.
Wyszukiwarka kodu na Githubie może nam pomóc zrozumieć co się tu dzieje:
https://github.com/search?q=repo%3Asipeed%2FM1s_BL808_SDK+m1s_xram_wifi_connect&type=code
Wyszukiwana metoda znajduje się w M1s_BL808_SDK/components/sipeed/c906/m1s_c906_xram/src
/m1s_c906_xram_wifi.c:
Kod: C / C++
Wypełnia ona strukturę reprezentującą operację WiFi:
Kod: C / C++
Następnie przekazuje ją dalej:
Kod: C / C++
Powyższa funkcja pozwala na dostęp do zasobu współdzielonego między rdzeniami, czyli właśnie do sekcji XRAM. Wszystkiego pilnuje mutex (skrót od mutual exclusion), czyli system blokad, który zapewnia, że tylko jeden wątek na raz może uzyskać dostęp do zasobu XRAM. Jest to istotne w kontekście wielowątkowego programowania, ponieważ bez odpowiedniej synchronizacji dostępu do zasobów współdzielonych mogą pojawić się błędy i nieprzewidywalne wyniki działania programu.
Następnie, gdy jest już pewność, że tylko my dobieramy się do pamięci, zapisywane do tej są bajty struktury reprezentującej komendę poprzez XRAMRingWrite.
Co dalej z tą komendą?
Wyszukajmy po jej kodzie - XRAM_WIFI_CONNECT:
https://github.com/search?q=repo%3Asipeed%2FM1s_BL808_SDK%20XRAM_WIFI_CONNECT&type=code
Wcześniej operowaliśmy na rdzeniu c906, teraz patrzymy na e907.
Poniżej widzimy funkcję, która odczytuje operację ze współdzielonej pamięci RAM oraz odpowiednio wywołuje jej obsługę:
Kod: C / C++
Przykładowo, dla XRAM_WIFI_CONNECT, wywoływana jest funkcja xram_wifi_connect:
Kod: C / C++
Powyższa funkcja realizuje dwie operacje. Najpierw wywołuje połączenie się przez WiFi, czyli funkcję wifi_connect. Następnie zapisuje ona odpowiedź do współdzielonej pamięci RAM, by drugi rdzeń mógł poznać ewentualne rezultaty tego działania.
Analogicznie obsługiwana jest komenda XRAM_WIFI_UPLOAD_STREAM:
https://github.com/search?q=repo%3Asipeed%2FM...8_SDK%20XRAM_WIFI_UPLOAD_STREAM&type=code
Poniżej kod źródłowy już samej implementacji wysyłania obrazów:
Kod: C / C++
Widzimy tutaj, że obrazy przesyłane są przez TCP jako pliki JPG, ale o tym więcej za chwilę.
W ten sposób realizowana jest komunikacja pomiędzy rdzeniami. Warto zapoznać się z pełnym kodem obu "stron" xram dostępnym w folderach poniżej:
https://github.com/sipeed/M1s_BL808_SDK/tree/main/components/sipeed/e907/m1s_e907_xram
https://github.com/sipeed/M1s_BL808_SDK/tree/main/components/sipeed/c906/m1s_c906_xram
Program na PC i końcowe efekty
Program na PC jest napisany w Pythonie, stanowi on serwer TCP czekający na połączenie. Potrzebny będzie Python 3 oraz opencv. Uruchamiamy go tak:
py ./main.py
C:\Users\Tester\Downloads\M1s_BL808_example-main\M1s_BL808_example-main\c906_app\camera_streaming_through_wifi>py ./main.py
Traceback (most recent call last):
File "./main.py", line 3, in <module>
import cv2 as cv
ModuleNotFoundError: No module named 'cv2'
Zatem instalujemy opencv:
pip install opencv-python
Potem program powinien się uruchomić. Warto jeszcze sprawdzić, czy w ogóle BL808 podłączyło się do naszego WiFi:
Uruchamiamy program i sukces - odbieramy obraz przekazywany na żywo z kamerki:
Zobaczmy skrypt Python, który za to odbieranie odpowiada:
Kod: Python
Powyższy program jest naprawdę bardzo prosty. Nasłuchuje czekając na połączenia TCP a potem obsługuje takie połączenie w pętli, po kolei odczytując nagłówki i dane plików JPG, które potem są wyświetlane na ekranie.
Przerabiamy demko na serwer HTTP - część 1
W oryginalnym przykładzie użyty jest czysty TCP, BL808 jest klientem, a maszyna z Pythonem jest serwerem. Wymaga to instalacji Pythona oraz ogólnie nie jest wygodne w użyciu.
Z tego powodu proponuję wykonać prostą modyfikację tego przykładu tak, aby od teraz BL808 był serwerem i to HTTP, takim który jest w stanie przetworzyć GET od każdego zwyczajnego urządzenia z przeglądarką, np. z telefonu.
Zasadniczo aby to zrobić musimy:
- postawić prosty serwer TCP na BL808
- potem na tym serwerze odbierać pakiety, pakiety już HTTP, czyli przetworzyć żądanie GET HTTP w jakiś prosty sposób (właściwie to jeśli mamy tylko jeden zasób na tym serwerze, to nawet nie ma co przetwarzać)
- odpowiedzieć potem na te żądanie strumieniem bajtów nagłówek HTTP z odpowiednim content-type oraz zawartością, w tym przypadku będą to dane pliku JPG
Najpierw zatem tworzymy własne zdarzenie, poczynając od własnej enumeracji:
Kod: C / C++
Dodajemy też funkcje wysyłającą i odbierającą przez XRAM:
Kod: C / C++
Oraz implementujemy jego obsługę na rdzeniu od WiFi:
Kod: C / C++
Powyższy kod tworzy serwer HTTP który nasłuchuje na porcie 80 i w pętli obsługuje klientów.
Samą obsługę klienta przedstawia funkcja poniżej, na próbę zrobiłem wysyłanie napisu 'Hello world' przez HTTP:
Kod: C / C++
Powyższa funkcja nie sprawdza co wysłała przeglądarka, na ślepo odpowiada zawsze HTTP 200 OK z tekstem (content-type text) 'Hello world'. To na próbę.
Sam task serwera HTTP podpinamy do wcześniej napisanej obsługi zdarzenia:
Kod: C / C++
Potem musimy skompilować i wgrać oba wsady. Końcowo, po wejściu na adres IP kamerki otrzymamy:
Wygląda na to, że wszystko działa.
Własny serwer HTTP przedstawiający obraz z kamery
Mamy już prosty serwer 'hello world', teraz wystarczy go zmodyfikować.
Tak jak pisałem - na żądanie GET odpowiemy odsyłając plik JPG który mamy już zasadniczo gotowy w firmware, wraz z odpowiednim content-type.
Całość jak wcześniej, w wątku, a w nim w pętli - obsługa jednego żądania po drugim.
Poniżej pełen kod który opracowałem:
Kod: C / C++
Poza przekopiowaną obsługą kamery z demka wysyłania obrazu przez TCP oraz mojego serwera HTTP z poprzedniego akapitu nie ma tu nic nowego za wyjątkiem lepszego tworzenia odpowiedzi HTTP 200 OK:
Kod: C / C++
Nagłówek HTTP 200 OK musi zawierać zarówno pole Content-Type, jak i Content-Length aby przeglądarka wiedziała jak dużej liczby bajtów się spodziewać i jak je zinterpretować.
Ten serwer HTTP jest naprawdę bardzo prymitywny. W zasadzie odbiera on tylko dane na danym porcie TCP i od razu na nie odpowiada, wcale ich nie sprawdzając. Nie parsuje nawet żądania GET, po prostu na ślepo wysyła odpowiedź o odpowiednim typie content-type, tutaj pliku jpg.
To wystarcza, by móc pobrać obraz z kamerki:
Powyższa implementacja ma jedną wadę - czasem nie pobiera całego obrazu, muszę sprawdzić czemu tak jest, może muszę dopisać sleep. Będę ją jeszcze rozwijać.
Oczywiście tu jeszcze zostaje kwestia skąd to wywołać, ale mamy dwie opcje - albo można tym zastąpić stary kod kamerki która była w formie klienta, albo można zaimplementować osobną komendę XRAM. To już zostawiam czytelnikowi, chociaz możliwe, że spróbuję podesłać mój kod jako kolejny przykład do repozytorium B1S Dock jako pull request, jeśli go przyjmą to zaktualizuję temat.
Podsumowanie
Nie była to pełnoprawna kamera internetowa gdyż obrazy były wysyłane jeden po drugim (jako pliki JPG) a nie jako strumień video, ale i tak myślę, że może kogoś to zainteresuje. Ewentualne streamowanie video na tej platformie dopiero muszę odpalić, ale już teraz można by ją wykorzystać jako chociażby fotopułapka lub prosty podgląd video przez WiFi.
Przy okazji też nauczyliśmy się jak zrealizować komunikację pomiędzy rzdzeniami na BL808, ale tu akurat to była kwestia drugorzędna.
Po więcej szczegółów odsyłam do SDK i dokumentacji:
https://wiki.sipeed.com/hardware/en/maix/m1s/other/start.html
https://github.com/sipeed/M1s_BL808_example
https://github.com/sipeed/M1s_BL808_SDK
Fajne? Ranking DIY Pomogłem? Kup mi kawę.
