Implementacja funkcji Screen Mirroring na platformach Raspberry Pi oraz SoMLabs VisionSOM-6ULL

Kiedy 8 lat temu w prasie zaczęły pojawiać się pierwsze informacje dotyczące komputera jednopłytkowego Raspberry Pi został on głośno okrzyknięty pogromcą typowych komputerów PC w zastosowaniach biurowych i edukacyjnych. Pomimo tego, że szumnie ogłaszana rewolucja nie nastąpiła, to komputer Raspberry Pi dość mocno wpisał się w najnowszą historię informatyki i jest obecnie coraz częściej spotykany w standardowym użyciu, nawet przez osoby nie związane zawodowo z systemami wbudowanymi. Niech potwierdzeniem tych słów będzie fakt, że w ostatnich miesiącach miałem przyjemność brać udział w nietechnicznym szkoleniu, którego prowadzący zdecydował się na wykorzystanie Raspberry Pi, jako niewielkiego i energooszczędnego zamiennika typowego komputera PC. Podczas prezentacji pojawiła się jednak potrzeba jednoczesnego zaprezentowania na rzutniku informacji zawartych na ekranie telefonu prowadzącego, jak i samej prezentacji – czy tak postawiony problem można rozwiązać w sposób czysto programowy, bez użycia dodatkowych konwerterów sprzętowych? Spróbujmy znaleźć odpowiedź na to pytanie.

W niniejszym artykule zaprezentowane zostaną dwa proste sposoby realizacji funkcjonalności Screen Mirroring, czyli przesłania i wyświetlenia zawartości ekranu urządzenia mobilnego na zewnętrznych ekranach stacjonarnych. Do realizacji tego zadania wykorzystany zostanie telefon z systemem Android oraz dwa komputery jednopłytkowe – wydajny pod kątem aplikacji multimedialnych Raspberry Pi 4, a także przystosowany głównie do zadań kontrolno-pomiarowych, zestaw SoMLabs VisionSOM-6ULL z energooszczędnym procesorem NXP i.MX6ULL. Wybór dwóch różnych platform sprzętowych pozwoli na dobór różnych rozwiązań programowych, dopasowanych pod kątem możliwości oferowanych przez sprzęt – od prostych wywołań z pakietu funkcji dostarczanych przez projekt GStreamer, aż do kompilacji otwarto-źródłowego i funkcjonalnego projektu scrcpy.

Screen Mirroring na platformie SoMLabs VisionSOM-6ULL

Proces implementacji funkcji realizującej Screen Mirroring rozpoczniemy od wykorzystania platformy VisionSOM-6ULL wraz z dedykowanym wyświetlaczem LCD oraz modułem wyposażonym w gniazdo karty pamięci SD. Pomimo, że procesory i.MX6ULL nie posiadają wyspecjalizowanych układów sprzętowych do dekodowania strumieni wideo i wsparcia dla grafiki 2D/3D (co przekłada się na znacznie niższy koszt procesora w porównania do lepiej wyposażonych układów z rodziny i.MX), możliwa jest wydajna implementacja obsługi przesyłania, a także dekodowania obrazu. Operacja ta wymaga jednak od użytkownika systemu nieco więcej zabiegów programowych w procesie przygotowania systemu.

Prace nad projektem rozpoczynamy od przygotowania karty z systemem operacyjnym Linux. Producent komputera – firma SoMLabs – na stronach Wiki swojego produktu dostarcza użytkownikowi wsparcie w postaci gotowych obrazów z dystrybucją Debian. Dostępny jest także opis budowy obrazów z wykorzystaniem takich narzędzi jak Buildroot oraz Yocto. W niniejszym artykule wykorzystamy gotowe obrazy systemu z dystrybucją Debian w wersji Stretch. Na stronie producenta udostępniono również nowszą wersję dystrybucji – Debian Buster – z wbudowaną obsługą dedykowanego wyświetlacza LCD.

Przygotowanie karty SD

Przygotowanie karty SD rozpoczynamy od pobrania obrazu systemu. W środowisku Linux operację tę można zrealizować poleceniem:

Po pobraniu pliku rozpakujmy jego zawartość za pomocą narzędzia unxz:

Po pobraniu oraz rozpakowaniu pliku można przystąpić do wgrania obrazu systemu na kartę micro-SD. Jedną najprostszych metod zapisania obrazu z pliku jest wykorzystanie linuksowego narzędzia dd.

Po wgraniu obrazu na kartę, możemy zalogować się do konsoli systemu wykorzystując wbudowany w płytę bazową VisionCB-STD konwerter UART-USB oraz dowolny program emulatora terminalu:

Po otwarciu połączenia należy zalogować się konto użytkownika root (bez hasła):

Wraz z obrazem Debian Stretch firma SoMLabs dostarcza użytkownikowi wygodne środowisko konfiguracji i kompilacji jądra systemu oraz plików Device Tree. Środowisko to zostało zbudowane w oparciu o narzędzia Qemu oraz chroot, co pozwala na uruchomienie na komputerze PC (np. x86) nienatywnych plików wykonywalnych (np. dla architektury ARM) w standardowy dla plików natywnych sposób, tj. poleceniem ./program. Do poprawnego działania środowiska w dystrybucji Ubuntu, niezbędna jest uprzednia instalacja pakietów qemu, binfmt-support oraz qemu-user-static:

Pobranie, rozpakowanie i uruchomienie (w tym wykonanie operacji chroot zmieniającej katalog główny) kompletnego środowiska:

Po uruchomieniu skryptu chtoolchain źródła systemu Linux są dostępne w katalogu /home/developer/source/kernel/linux-rel_imx_4.1.15_2.1.0_ga. Dla uproszczenia dalszego opisu, przejdźmy zatem do katalogu z kodem źródłowym:

W kolejnym kroku do katalogu arch/arm/configs należy skopiować domyślną konfigurację jądra (plik visionsom-6ull-linux_defconfig) udostępnioną przez producenta płytki:

W odróżnieniu od najnowszej wersji obrazu systemu Debian (Buster) dla płytki VisionSOM, opis Device Tree (w postaci pliku arch/arm/boot/dts/somlabs-visionsom-6ull.dts) w obrazie Debian Stretch nie zawierał pełnego wsparcia dla dedykowanego ekranu LCD. Aby poprawnie uruchomić wyświetlacz, niezbędne jest zatem pobranie i zaaplikowanie dodatkowej łatki:

Kompilacja jądra i Device Tree

W tak przygotowanym środowisku można dokonać konfiguracji i kompilacji jądra oraz opisu Device Tree za pomocą następujących poleceń:

Pliki wynikowe powyższych kompilacji, tj.:

powinniśmy skopiować do katalogu /boot na karcie SD z obrazem systemu. Po podłączeniu wyświetlacza oraz kabla Ethernet płytka jest gotowa do dalszych działań.

Zanim przejdziemy do działań związanych bezpośrednio z dekodowaniem i wyświetlaniem obrazu z urządzenia mobilnego, należy rozwiązać problem „przechwycenia” takiego obrazu. Pierwszą z opcji jest wykorzystanie gotowych rozwiązań, np. w postaci standardu Miracast, wspieranego przez największych producentów urządzeń mobilnych. Niestety implementacja takich rozwiązań może być niezwykle czasochłonna i/lub wymagać odpowiednich certyfikatów. Alternatywnym rozwiązaniem jest przygotowanie własnej aplikacji dla systemu Android. Może ona wykorzystać udostępnione dla programistów programistów MediaProjection API umożliwiające przechwytywanie zawartości ekranu, a także odpowiednio skompresować i wysłać dane do komputera jednopłytkowego. Takie rozwiązanie, choć zapewniające pełną swobodę w wyborze kompresji obrazu czy protokołu komunikacji, wymaga od programisty pewnego doświadczenia z systemem Android oraz językami Java lub Kotlin.

Czy można ten problem rozwiązać znacznie prościej? Można, wykorzystując do tego celu narzędzie ADB (Android Debug Bridge), przeznaczone do komunikacji i zarządzania urządzeniami Android z poziomu komputera PC. Program ADB został udostępniony dla wszystkich najpopularniejszych systemów operacyjnych (Windows, Linux, macOS), jednak ze względu na swój konsolowy interfejs nie znajduje on zastosowania w powszechnym użytku. Oprócz szeregu funkcji związanych z kopiowaniem plików, instalacją pakietów APK czy odczytem logów systemowych, program ADB udostępnia również możliwość wykonania zrzutów oraz przechwycenia zawartości ekranu urządzenia mobilnego. Właśnie tą opcję wykorzystamy do realizacji postawionego zadania.

Instalacja i konfiguracja ADB

Instalacja pakietu ADB na module VisionSOM-6ULL z systemem Debian, sprowadza się wyłącznie do jednego wywołania narzędzia apt:

Aby nawiązać komunikację z telefonem Android, niezbędne jest uprzednie odblokowanie na urządzeniu „Opcji programisty” oraz włączenie „Debugowania USB”. Włączenie opcji programisty można zrealizować poprzez siedmiokrotne kliknięcie opcji „Numer wersji” (lub „Numer kompilacji”) w Ustawieniach telefonu – rysunek 1.

Rys. 1. Odblokowanie „Opcji programisty” na urządzeniu mobilnym

Po odblokowaniu opcji programisty, włączeniu trybu debugowania oraz połączeniu telefonu z modułem VisionSOM-6ULL za pomocą kabla USB, możemy podjąć próbę sprawdzenia statusu połączenia pomiędzy urządzeniami. W tym celu w konsoli użytkownika wpisujemy polecenie adb devices. Wyświetli ono identyfikatory wszystkich podłączonych do komputera urządzeń z systemem Android. W przypadku wystąpienia problemów z nawiązaniem połączenia, warto sprawdzić komunikaty wyświetlane na ekranie telefonu. Mogą one wymagać potwierdzenia podpisu cyfrowego urządzenia lub udzielenia stosownych zezwoleń:

Do przechwycenia zawartości ekranu wykorzystamy polecenie adb shell screenrecord w postaci:

Tym samym, strumień wyjściowy poddamy kompresji w formacie H.264. Przepływność danych ustawiono na wartość 2 Mbit/s (domyślna wartość 20 Mbit/s znacznie przekracza możliwości płynnej dekompresji obrazu), a rozdzielczość obrazu dopasowano do rozdzielczości podłączonego wyświetlacza. Ostatnim z argumentów polecenia jest nazwa pliku wyjściowego, do którego na bieżąco będzie zapisywany przechwytywany obraz. W przedstawionym przypadku ostatni argument ma wartość „-”, co oznacza, że strumień wyjściowy będzie bezpośrednio zapisywany na standardowe wyjście. Próba uruchomienia tak zdefiniowanego polecenia zakończy się wyłącznie wyświetleniem szeregu nieczytelnych znaków – uzyskane dane należy przekazac do odpowiedniego dekodera.

Dekodowanie wideo za pomocą GStreamer

Do zdekodowania strumienia wyjściowego wykorzystany zostanie pakiet GStreamer, którego kompleksowa instalacja w systemie może zostać zrealizowana za pomocą poleceń:

Poprawność instalacji pakietów można przetestować komendą:

które wyświetli na dołączonym ekranie prosty wzór testowy – rysunek 2.

Rys. 2. Wyświetlenie wzoru testowego z wykorzystaniem funkcji GStreamer

Aby wspomóc procesor i.MX6ULL w operacjach związanych z grafiką i multimediami (wyświetlanie ekranu testowego z rysunku 2 realizowane jest w pełni programowo), niezbędne będzie zainstalowanie dodatkowych wtyczek dla pakietu GStreamer, dostarczanych i rozwijanych przez NXP. Zestaw wtyczek rozwijanych w ramach projektu gstreamer-imx, zawiera między innymi wsparcie dla sprzętowego modułu PXP (Pixel Processing Unit), wspomagającego operacje związane z przetwarzaniem obrazów 2D, konwersją przestrzeni kolorów, skalowaniem, itd. Zainstalujmy zatem w systemie pakiety i zależności niezbędne do poprawnej kompilacji projektu gstreamer-imx:

Kolejny krok stanowi pobranie źródeł projektu:

Do kompilacji gstreamer-imx niezbędne jest również zainstalowanie w systemie plików nagłówkowych jądra Linux. Dla obrazu Debian Stretch udostępniono je w postaci pakietu DEB, wraz z opisanym na wstępie artykułu plikiem somlabs-visionsom-6ull-debian-rootfs-qemu.tar.xz:

Konfiguracja i kompilacja gstreamer-imx

Projekt gstreamer-imx wykorzystuje do procesu konfiguracji i kompilacji narzędzie Waf – pełny przebieg tego procesu przedstawiono poniżej:

Po zakończonym procesie instalacji lista wtyczek dla projektu GStreamer powinna rozszerzyć się m. in. o wtyczkę imxpxpvideosink:

System wyposażono już w komplet narzędzi programowych. Poprawność działania wtyczek pakietu gstreamer-imx możemy w prosty sposób przetestować wyświetlając na ekranie fragment dowolnego filmu testowego (rysunek 3):

Rys. 3. Wyświetlenie filmu testowego z wykorzystaniem wtyczki imxpxpvideosink

Aby zdekodować i wyświetlić na ekranie obraz generowany przez polecenie adb shell screenrecord, połączymy polecenia adb oraz gst-launch za pomocą operacji potoku „|”, a jako źródło strumienia wejściowego dla GStreamer’a, wskażemy standardowe wejście (fdsrc fd=0):

Efekt działania powyższego polecenia przedstawiono na rysunku 4.

Rys. 4. Funkcja Screen Mirroring zbudowana z wykorzystaniem pakietów ADB oraz GStreamer

Przedstawiona powyżej metoda nie jest pozbawiona wad. Trzeba było obniżyć jakość przesyłanego obrazu ze względu na ograniczenia wynikające z dostępnych zasobów sprzętowych. Niemniej jednak pozwala ona na szybkie przesłanie zawartości ekranu telefonu do komputera z system Linux. Nie wymaga instalacji żadnego dodatkowego oprogramowania po stronie urządzenia mobilnego, a całość operacji jest realizowana za pomocą jednego polecenia. W przypadku większości dystrybucji systemu Linux, nie trzeba również instalować żadnego dodatkowego oprogramowania po stronie komputera (wspierany sprzętowo pakiet GStreamer jest instalowany domyślnie w większości dystrybucji), a zaprezentowana metoda może zostać również z powodzeniem wykorzystana na standardowych komputerach klasy PC z procesorami x86/x64.

Raspberry Pi – projekt scrcpy

Świat miłośników oprogramowania otwarto-źródłowego nie znosi pustki. Kwestią czasu było więc pojawienie się alternatywnych rozwiązań dla komercyjnego projektu Miracast czy niskopoziomowych rozwiązań jak to przedstawione powyżej. Jednym z takich otwartych projektów jest program scrcpy. Do realizacji zadania wykorzystuje on zarówno narzędzie ADB, jak i natywną aplikację dla systemu Android do przechwytywania i kompresji obrazu. Projekt ten dość intensywnie wykorzystuje funkcje dostarczane poprzez interfejs ADB, oferując tym samym nie tylko proste przesyłanie obrazu, ale jednocześnie obsługę wejścia (sterowanie telefonem z poziomu myszki i klawiatury na lustrzanej projekcji) czy instalację pakietów APK poprzez system Drag&Drop. Aplikacja jest dostępna dla systemów Windows, Linux oraz macOS. Nie jest jednak dostarczana w postaci gotowych pakietów dla platformy ARM – użytkownik musi samodzielnie wykonać kompilację kodu źródłowego.

Projekt scrcpy do dekodowania strumienia danych wykorzystuje biblioteki z projektu ffmpeg (libav), które na platformie i.MX6ULL nie posiada sprzętowego wsparcia. Całość obliczeń realizowania jest w sposób programowy, i jak przekonał się Autor, nie pozwala to uzyskać satysfakcjonujących wyników. Do kompilacji i uruchomienia projektu wykorzystamy więc komputer Raspberry Pi 4. Jego moc obliczeniowa jest w zupełności wystarcza do wygodnego korzystania z funkcji oferowanych przez scrcpy.

Przygotowanie platformy scrcpy

Po pobraniu i wgraniu na kartę SD obrazu systemu Raspberry Pi OS, proces przygotowania platformy rozpoczynamy od aktualizacji oprogramowania:

Jak wspomniano, projekt scrcpy wykorzystuje funkcje oferowane przez narzędzie ADB, tak więc kolejny krok stanowi zainstalowanie brakujących pakietów oraz zależności niezbędnych do kompilacji programu:

Pobranie kodu źródłowego scrcpy za pomocą narzędzia git:

Wraz z kodem źródłowym aplikacji dla systemów Windows i Linux, udostępniono również kod aplikacji dla systemu Android. Jej zadaniem jest przechwycenie, a także przesłanie obrazu. Aplikacja ta jest niezależna od architektury na jaką wykonujemy kompilację programu klienta. Deweloperzy umożliwili uproszczenie etapu konfiguracji i kompilacji projektu, poprzez udostępnienie gotowych plików APK:

Aby wykorzystać pliki APK w procesie konfiguracji, za pomocą argumentu prebuilt_server, wskazujemy pobrany w uprzednim kroku plik dla systemu Android:

Kompilacja źródeł umieszczonych w folderze ‘x’:

Zakończony proces kompilacji umożliwia nam bezpośrednie uruchomienie programu – bez potrzeby instalacji plików wynikowych:

Efekt uruchomienia aplikacji scrcpy przedstawiono na rysunku 5.

Rys. 5. Funkcja Screen Mirroring realizowana przez projekt scrcpy na platformie Raspberry Pi

Dodatkowo, program scrcpy dostarcza również szeregu opcji konfiguracyjnych związanych z ustawieniami rozdzielczości, przepływności, konfiguracją okna. Umożliwia również wygaszenie ekranu urządzenia mobilnego. Pełną listę opcji konfiguracyjnych uzyskamy po wydaniu polecenia:

Wykorzystując platformę Raspberry Pi warto również przetestować alternatywne rozwiązania. Są to modyfikacje metody wykorzystanej dla płytki VisionSOM-6ULL, polegające na podmienieniu pakietu GStreamer na wspierany sprzętowo odtwarzacz multimedialny omxplayer:

O autorze