Blog (7)
Komentarze (21)
Recenzje (0)

System typu CarPi część 2, QML/C++

@zoolekSystem typu CarPi część 2, QML/C++19.12.2016 21:08

Witam wszystkich ponownie.

Poniżej na filmie prezentuję przepisany od nowa, z użyciem technologii QML + logika C++ amatorski system typu Car Pi (a przynajmniej ja go tak nazywam).

Oczywiście, jak to zwykle bywa system jest w fazie gruntownej budowy, co za tym idzie nie zdecydowałem się pokazać wszystkich funkcjonalności, żeby nie było jakiegoś Segmentation fault ;)

Sprzęt, na którym to wszystko działa to jeden Raspberry Pi 2 (jako główny sterownik) i po jednym Raspberry Pi B+ na kamerę (kamera Sony IMX219 do rpi). Do tego tani, chiński wyświetlacz z aliexpress za ok. 100 zł, moduł GPS z Kamami, modem Huawei E3131 do wysyłania danych o współrzędnych na serwer i oglądania youtube'a, oraz kilka innych gadżetów. Na filmie prezentuję w postaci developerskiej, bez obudowy, ponieważ cały czas dokonuję pewnych modyfikacji w bebechach.

Od strony programowej:

Na sterowniku głównym i kamerach Raspbian Jessie. Własnoręcznie skompilowane Qt 5.7.0, QtGstreamer i kilka innych, też ważnych komponentów.

Do poprawnego działania touchscreen'a jest potrzebny tslib, czyli biblioteka obsługująca panele dotykowe, w moim przypadku model Bus 001 Device 004: ID 0eef:0001 D-WAV Scientific Co., Ltd eGalax TouchScreen

1. Odtwarzanie/nagrywanie obrazu z kamer zamontowanych z przodu i z tyłu pojazdu

Zdecydowałem się, żeby przesyłać obraz w chyba najbardziej powszechnym standardzie, mianowicie h264. Na kamerach jest zainstalowany prosty soft obsługujący autorejestrację kamery w systemie, oraz obsługujący raspivid'a i gstreamera. Śmiało można skorzystać ze zwykłego pipelina uruchamianego z basha, ale chciałem mieć możliwość zmiany pewnych parametrów obrazu, czego uruchomiony z basha raspivid nie umożliwia.

raspivid -t 0 -w 1280 -h 720 -fps 30 -n -b 10000000 -o - | gst-launch-1.0 -vvv fdsrc ! h264parse ! rtph264pay config-interval=1 pt=96 ! udpsink host=225.0.0.0 port=2050 sync=false

Po stronie sterownika sprawa była trochę bardziej skomplikowana. Standardowy playbin gstreamerowy nie "łyka" strumieni RTP. Ze strumieniem RTSP miałem taki problem, że opóźnienia obrazu sięgały po kilka sekund. I co tu zrobić...

Z pomocą przychodzi być może niektórym znany QtGstreamer. Jest to binding C++ gstreamera. Strona domowa projektu: https://gstreamer.freedesktop.org/modules/qt-gstreamer.html

W przykładach znalazłem, jak odtwarzać coś playbinem (wszyscy uczepili się tego playbina). Nic o strumieniach RTP. Dlatego musiałem trochę pokminić i w końcu wyszło ! Zapodaję fragment kodu:

[code=C++]bool StreamPlayer::setUri(const QString &host, const QString &port) { if (streamContainer.count() == 0) return false; stop();

if (m_pipeline.isNull() && !m_videoSink.isNull()) { m_pipeline = QGst::Parse::launch(QString("udpsrc name=src multicast-iface=eth0 address=%1 port=%2 caps=\"application/x-rtp\" ! rtph264depay ! h264parse disable-passthrough=true ! omxh264dec name=omx").arg(host).arg(port)).dynamicCast<QGst::Pipeline>();

if (!m_pipeline.isNull()) { udpsrc = m_pipeline->getElementByName("src");

if (udpsrc.isNull()) return false; udpsrc->setProperty("timeout", UDPSRC_TIMEOUT);

omx = m_pipeline->getElementByName("omx");

if (omx.isNull()) return false;

m_videoSink->setProperty("sync", false); m_pipeline->add(m_videoSink);

omx->link(m_videoSink);

QGst::BusPtr bus = m_pipeline->bus(); bus->addSignalWatch(); return QGlib::connect(bus, "message", this, &StreamPlayer::onBusMessage); } else return false; } return false; }[/code]

Rzecz polega na tym, że trzeba pobrać element dekodera i zlinkować go z sinkiem, który to jest elementem wyświetlającym obraz w kontrolce QMLowej. Udało się, co widać na filmie.

Nagrywanie video wygląda tak od strony gstreamer'a:

[code=C++]void StreamRecorder::setStream(QString host, QString port, QString location) { bin = QGst::Bin::fromDescription(QString("udpsrc multicast-iface=eth0 address=%1 port=%2 caps=\"application/x-rtp\" ! rtpjitterbuffer latency=1 ! rtph264depay ! h264parse ! matroskamux ! filesink sync=false location=%3 name=sink").arg(host).arg(port).arg(location));

if (!m_pipeline) { m_pipeline = QGst::Pipeline::create();

m_pipeline->add(bin);

if (m_pipeline) { QGst::BusPtr bus = m_pipeline->bus(); bus->addSignalWatch(); QGlib::connect(bus, "message", this, &StreamRecorder::onBusMessage); } else { qCritical() << "Failed to create the pipeline"; } } }[/code]

[code=C++]bool StreamRecorder::start() { if (m_pipeline) { if (m_pipeline->setState(QGst::StatePlaying) == QGst::StateChangeSuccess) return true; }

return true; }[/code]

2. Radio internetowe

W przypadku radia internetowego sprawa była dosyć prosta i faktycznie wystarczył poczciwy playbin. Radio to nic innego jak strumień mp3, który playbin "łyka". Kilka prostych kontrolek typu ListView i komponent Audio

[code=QML]import QtMultimedia 5.7 Audio { id: radioPlayer

onPlaying: { toolbarRadioImage.show() streamPlayButtonImage.visible = true radioMetaDataTimer.running = true }

onStopped: { toolbarRadioImage.hide() streamPlayButtonImage.visible = false radioMetaDataTimer.running = false radioTitleText.text = "" } } [/code]

i lista stacji radiowych, póki co w ListModel:

[code=QML]import QtQuick 2.0

ListModel { id: radioModel

ListElement { name: "Radio Rzeszów" url: "http://radiointernetowe.net:9500/;?.mp3" image: "file:///home/pi/radio/radiorzeszow.jpg" } ListElement { name: "Radio Plus" url: "http://s3a.deb1.scdn.smcloud.net/t051-1.mp3" image: "file:///home/pi/radio/plusradio.jpg" } .....[/code]

Część stacji radiowych w swoim strumieniu umieszcza tak zwane meta data, czyli dane o np. tytule granego utworu lub nazwę audycji. Kontrolka Audio z QtMultimedia umożliwia pobranie takich danych:

[code=QML]radioTitleText.text = radioPlayer.metaData.title[/code]

Nie wszystkie stacje to nadają, a niektóre w ogóle nadają tytuł w innych właściwościach niż title, więc nie dociekałem, która stacja radiowa wypełnia prawidłowo swoje dane i w przypadku braku tytułu nie wyświetlam nic.

3. Odtwarzanie filmów

Sytuacja dosyć ciekawa, ponieważ mało inteligentny playbin sam dobiera sobie za pomocą priorytetów wtyczek elementy potrzebne do odtworzenia konkretnego materiału. Już myślałem, że nic z tego nie będzie, ponieważ mając zainstalowaną wtyczkę gstreamer1.0-libav playbin odtwarzał mi video bez akceleracji sprzętowej (bez omxh264dec). Trzeba to wyrzucić (sudo apt-get purge gstreamer1.0-libav). Trzeba również wyrzucić gstreamer1.0-vaapi i po problemie. Playbin powinien odtwarzać h264 za pomocą wtyczki omxh264dec. Osadzenie video w QMLu to już banalna sprawa:

[code=QML] Video { anchors.fill: parent id: video width : 800 height : 450 fillMode: VideoOutput.Stretch visible: true ....[/code]

4. Youtube

Dużo by pisać, więc będzie w skrócie.

Użyłem klawiatury ekranowej z Qt (import QtQuick.VirtualKeyboard 2.1). Zapytania do Youtube'a wysyłam za pomocą Youtube Data API v3, czy jak to tam się nazywa =>https://developers.google.com/youtube/v3/

onAccepted: {
                request('https://www.googleapis.com/youtube/v3/search?part=snippet&q=' + text + '&maxResults=10&type=video&key=TWÓJ KLUCZ DO API', function (o) {
                    //console.log("text: " + text);
                    // log the json response
                    if (o.responseText.length >= 0)
                    {
                        listVideos.model.clear()
                        jsonModel1.query = "$.items[*]"
                        jsonModel1.json = o.responseText
                        jsonModel1.updateJSONModel()
                        console.log(o.responseText);
                    }
                });
            }

a w odpowiedzi otrzymuję JSON'a, którego należy sobie sparsować i wrzucić do kontrolki ListView. Odpowiedź dla zapytania "Abc" wygląda tak (na filmie użyłem tego przykładu):

{
 "kind": "youtube#searchListResponse",
 "etag": "\"gMxXHe-zinKdE9lTnzKu8vjcmDI/Dx5Bc6s3ApegoIShHqIxhKtArjc\"",
 "nextPageToken": "CAoQAA",
 "regionCode": "PL",
 "pageInfo": {
  "totalResults": 1000000,
  "resultsPerPage": 10
 },
 "items": [
  {
   "kind": "youtube#searchResult",
   "etag": "\"gMxXHe-zinKdE9lTnzKu8vjcmDI/93TOo770hWgptbMI_yPH_ahckgM\"",
   "id": {
    "kind": "youtube#video",
    "videoId": "_UR-l3QI2nE"
   },
   "snippet": {
    "publishedAt": "2014-05-01T11:12:58.000Z",
    "channelId": "UCbCmjCuTUZos6Inko4u57UQ",
    "title": "ABC SONG | ABC Songs for Children - 13 Alphabet Songs & 26 Videos",
    "description": "ABC Song and Alphabet Song Ultimate kids songs and baby songs Collection with 13 entertaining \"English abcd songs\" and 26 a to z fun Alphabet episodes, ...",
    "thumbnails": {
     "default": {
      "url": "https://i.ytimg.com/vi/_UR-l3QI2nE/default.jpg",
      "width": 120,
      "height": 90
     },
     "medium": {
      "url": "https://i.ytimg.com/vi/_UR-l3QI2nE/mqdefault.jpg",
      "width": 320,
      "height": 180
     },
     "high": {
      "url": "https://i.ytimg.com/vi/_UR-l3QI2nE/hqdefault.jpg",
      "width": 480,
      "height": 360
     }
    },
    "channelTitle": "ABCkidTV - Nursery Rhymes",
    "liveBroadcastContent": "none"
   }
  },

Mamy tutaj tytuł, opis, datę publikacji i thumbnailsy do filmików. W zasadzie to wystarczy.

Po naciśnięciu na obrazek filmu, po prawej stronie pokazuje się ten obrazek, a pod nim tytuł filmu. Jeszcze tylko kliknąć na ten obrazek i odtwarzamy video z Youtube'a !

Do wyświetlenia Youtube'a użyłem dosyć świeżego pomysłu developerów Qt, mianowicie kontrolki WebEngineView, która potrafi być czasem niestabilna i ciężko ją skompilować z ffmpeg, który by to dawał sprzętowe wsparcie dekodowania h264 w HTML5. Cóż, przy takiej rozdzielczości, jak wyświetlacz sterownika (800x480) nie potrzebuję ani 1080p, ani 4K ;) (ale nie ukrywam, pracuję nad tym.. ;)

[code=QML]WebEngineView { id: webView anchors.fill: parent opacity: 0

url: ""

onLoadingChanged: { switch (loadRequest.status) { case WebEngineView.LoadSucceededStatus: animationWebLoading.visible = false webView.visible = true return case WebEngineView.LoadStartedStatus: webView.visible = false animationWebLoading.visible = true break case WebEngineView.LoadStoppedStatus: break case WebEngineView.LoadFailedStatus: break } }

onFullScreenRequested: { if (request.toggleOn) { webView.state = "FullScreen" } else { webView.state = "" } request.accept() } }[/code]

5. Przeglądarka internetowa

Jest to również funkcjonalność oparta na kontrolce WebEngineView i bawię się tym i traktuję raczej jako ciekawostkę, ponieważ jak już wcześniej wspominałem, jest to element nieco niedopracowany, dodatkowo moc obliczeniowa RPi2 nie pozwala na płynne przeglądanie stron internetowych.

Inne

Projekt jest, jak sami widzicie w fazie rozwojowej. To, czego przede wszystkim nie widać na ekranie to logika oprogramowania zaszyta w kodzie. Podpinanie pendrive'a do usb, wysyłanie współrzędnych na serwer, czytanie parametrów z OBD2 samochodu, obsługa modemu (siła sygnału, sieć, do której się karta sim zarejestrowała) i wiele, wiele innych.

Dużo problemów sprawiają elementy opensource'owe, bo tylko z takich korzystam i niejednokrotnie były potrzebne pewne modyfikacje kodu, aby coś zaczęło działać.

Brakuje jeszcze odtwarzania muzyki z pendrive'a, wizualizacji OBD2 i panelu z ustawieniami, ale kto wie, co przyniesie 2017 rok, być może więcej czasu ;)

Do tego wszystkiego dochodzi obudowa na wyświetlacz, przetwornica 12V/5V do zasilenia urządzeń i koncepcja, jak to wszystko umieścić w samochodzie.

Jeśli coś wydało Ci się niejasne, jeśli masz jakieś pytania, zadawaj je śmiało w komentarzach. Z pewnością coś pominąłem, z pewnością nie wszystko do końca opisałem. Również, jeśli wydaje Ci się, że się w czymś pomyliłem i coś przekręciłem, napisz.

Pozdrawiam i dziękuję za poświęcony na przeczytanie tego wpisu czas !

A poniżej filmik z prezentacją systemu ;)

Szanowna Użytkowniczko! Szanowny Użytkowniku!
×
Aby dalej móc dostarczać coraz lepsze materiały redakcyjne i udostępniać coraz lepsze usługi, potrzebujemy zgody na dopasowanie treści marketingowych do Twojego zachowania. Twoje dane są u nas bezpieczne, a zgodę możesz wycofać w każdej chwili na podstronie polityka prywatności.

Kliknij "PRZECHODZĘ DO SERWISU" lub na symbol "X" w górnym rogu tej planszy, jeżeli zgadzasz się na przetwarzanie przez Wirtualną Polskę i naszych Zaufanych Partnerów Twoich danych osobowych, zbieranych w ramach korzystania przez Ciebie z usług, portali i serwisów internetowych Wirtualnej Polski (w tym danych zapisywanych w plikach cookies) w celach marketingowych realizowanych na zlecenie naszych Zaufanych Partnerów. Jeśli nie zgadzasz się na przetwarzanie Twoich danych osobowych skorzystaj z ustawień w polityce prywatności. Zgoda jest dobrowolna i możesz ją w dowolnym momencie wycofać zmieniając ustawienia w polityce prywatności (w której znajdziesz odpowiedzi na wszystkie pytania związane z przetwarzaniem Twoich danych osobowych).

Od 25 maja 2018 roku obowiązuje Rozporządzenie Parlamentu Europejskiego i Rady (UE) 2016/679 (określane jako "RODO"). W związku z tym chcielibyśmy poinformować o przetwarzaniu Twoich danych oraz zasadach, na jakich odbywa się to po dniu 25 maja 2018 roku.

Kto będzie administratorem Twoich danych?

Administratorami Twoich danych będzie Wirtualna Polska Media Spółka Akcyjna z siedzibą w Warszawie, oraz pozostałe spółki z grupy Wirtualna Polska, jak również nasi Zaufani Partnerzy, z którymi stale współpracujemy. Szczegółowe informacje dotyczące administratorów znajdują się w polityce prywatności.

O jakich danych mówimy?

Chodzi o dane osobowe, które są zbierane w ramach korzystania przez Ciebie z naszych usług, portali i serwisów internetowych udostępnianych przez Wirtualną Polskę, w tym zapisywanych w plikach cookies, które są instalowane na naszych stronach przez Wirtualną Polskę oraz naszych Zaufanych Partnerów.

Dlaczego chcemy przetwarzać Twoje dane?

Przetwarzamy je dostarczać coraz lepsze materiały redakcyjne, dopasować ich tematykę do Twoich zainteresowań, tworzyć portale i serwisy internetowe, z których będziesz korzystać z przyjemnością, zapewniać większe bezpieczeństwo usług, udoskonalać nasze usługi i maksymalnie dopasować je do Twoich zainteresowań, pokazywać reklamy dopasowane do Twoich potrzeb. Szczegółowe informacje dotyczące celów przetwarzania Twoich danych znajdują się w polityce prywatności.

Komu możemy przekazać dane?

Twoje dane możemy przekazywać podmiotom przetwarzającym je na nasze zlecenie oraz podmiotom uprawnionym do uzyskania danych na podstawie obowiązującego prawa – oczywiście tylko, gdy wystąpią z żądaniem w oparciu o stosowną podstawę prawną.

Jakie masz prawa w stosunku do Twoich danych?

Masz prawo żądania dostępu, sprostowania, usunięcia lub ograniczenia przetwarzania danych. Możesz wycofać zgodę na przetwarzanie, zgłosić sprzeciw oraz skorzystać z innych praw wymienionych szczegółowo w polityce prywatności.

Jakie są podstawy prawne przetwarzania Twoich danych?

Podstawą prawną przetwarzania Twoich danych w celu świadczenia usług jest niezbędność do wykonania umów o ich świadczenie (tymi umowami są zazwyczaj regulaminy). Podstawą prawną przetwarzania danych w celu pomiarów statystycznych i marketingu własnego administratorów jest tzw. uzasadniony interes administratora. Przetwarzanie Twoich danych w celach marketingowych realizowanych przez Wirtualną Polskę na zlecenie Zaufanych Partnerów i bezpośrednio przez Zaufanych Partnerów będzie odbywać się na podstawie Twojej dobrowolnej zgody.