Blog (7)
Komentarze (21)
Recenzje (0)
@zoolekSystem typu CarPi część 2, QML/C++

System 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 ;)

Wybrane dla Ciebie
Komentarze (10)