Portál AbcLinuxu, 1. května 2025 08:38
V dnešnom blogu sa pozrieme na API rozšírenia X video. Ukážeme si ako je možné použiť volanie XvPutImage pre akcelerované zobrazovanie YUV / RGB pixmapy.
Rozšírenie XVideo (skrátené Xv) bolo navrhnuté pre akcelerované zobrazovanie videa. Xv nie je žiadnou novinkou, jeho aktuálna major verzia bola vydaná už v roku 1991 (tj. v čase písania tohto blogu má vyše 21 rokov).
API je pomerne minimalistické, čo je aj jedným z dôvodov prečo vydržalo tak dlhú dobu bez výraznejších zmien. Autori zrejme neočakávali dlhú životnosť, takže v dokumentácii môžme nájsť vetu: "So, the life expectancy of Xv is not long." API poskytuje najnutnejšie metódy pre určenie počtu a typu adaptrérov, pripojenie sa na výstupný port adaptéru, nastavenie atribútov ako jas, kontrast, saturácia a v neposlednom rade aj pre samotný vstup / výstup obrazových dát.
Pre prezentáciu obrazu na výstupnom zariadení disponuje metódami PutImage, PutStill a PutVideo. V súčasných aplikáciách sa používa len prvá metóda. Zvyšné nemajú zvyčajne žiadnu podporu u ovládačoch.
Spôsob renderovania obrazu závisí od použitého WM. Pri nekompozitnom WM je možné použiť "video overlay" adaptér, kde sa okno renderuje s určitou farbou a samotné video pridáva do výsledného obrazu až grafická karta. S kompozitným WM sa používa "textured video" adaptér, ktorý renderuje video do textúry. Tá sa v kompozitnom WM mapuje cez pixelbuffery na plochu okna. Pri takomto spôsobe renderovania je možné z videa urobiť aj screenshot. Video overlay z princípu svojho fungovania tvorbu screenshotov neumožňuje.
Qt je multiplatformový framework. Pre použitie Xv budeme potrebovať pracovať priamo s X, čo znamená aj porušenie multiplatformovosti. Dúfam, že varovanie je dostatočne odstrašujúce na to, aby sme ho mohli ďalej ignorovať a tváriť sa, že neexistuje
Pri všetkých volaniach budeme potrebovať referenciu na Display
. U Qt 4 to zariadíme includovaním #include <QX11Info>
a zavolaním metódy widget.x11Info().display();
.
U Qt 5 je možné použiť Xlib jedine s xcb backendom. Pre prístup k display-u budeme potrebovať privátne hlavičky. Includujeme teda <QWindow>
a <qpa/qplatformnativeinterface.h>
. Pre Qt 5 bude potrebný nasledujúci kód:
static_cast<Display *>( qGuiApp->platformNativeInterface() ->nativeResourceForWindow("display", new QWindow(/* screen */)) );
Ak si náš widget pre renderovanie Xv nazveme QtXvWidget
bude kód pre získanie referencie na Display
vyzerať nasledovne:
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) #include <QWindow> #include <qpa/qplatformnativeinterface.h> #else #include <QX11Info> #endif #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/extensions/Xv.h> #include <X11/extensions/Xvlib.h> Display *QtXvWidget::getDpy() const { #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) QWindow *window = new QWindow(/* screen */); Display *dpy = static_cast<Display *>(qGuiApp->platformNativeInterface()->nativeResourceForWindow("display", window)); delete window; return dpy; #else return x11Info().display(); #endif }
Pre Qt 5 potrebujeme do ciest kompilátora pridať privátne hlavičky Qt 5. To je možné pridaním gui-private
do .pro
súboru.
QT += core gui multimedia contains(QT_VERSION, ^5\\..*) { QT+= gui-private widgets }
Aby bolo možné vykresľovať na plochu widgetu priamo pomocou Xlib musíme zaistiť, aby Qt widget neprekresľovalo. Na to stačí vypnúť double buffering a vykresľovanie systémového pozada.
widget.setAttribute(Qt::WA_PaintOnScreen, true); widget.setAttribute(Qt::WA_NoSystemBackground, true);
Dostupnosť rozšírenia Xv sa dá preveriť volaním funkcie XvQueryExtension
. Ak je rozšírenie dostupné vráti Success
. Nasledujúci kód vypíše verziu Xv za predpokladu, že je dostupná.
unsigned int version, release, request_base, event_base, error_base; if (XvQueryExtension(getDpy(), &version, &release, &request_base, &event_base, &error_base) == Success) { qDebug() << "X-Video Extension version" << (QString::number(version) + "." + QString::number(release)).toAscii().constData();
Po spustení sa vypíše niečo ako X-Video Extension version 2.2
.
Adaptér je cieľové zariadenie, na ktoré budeme posielať obraz. Jedna grafická karta môže mať aj niekoľko adaptérov. V prípade Intel GMA sú napríklad dostupné 2 adaptéry. Zoznam adaptérov je dostupný cez volanie XvQueryAdaptors
.
Štruktúra XvAdaptorInfo
obsahuje pre každý adaptér jeho názov (name
), typ (type
) adaptéra, číslo prvého portu (base_id
) a počet portov (num_ports
). Jeden adaptér zvyčajne umožňuje vykresľovať viacej okien súčasne pomocou rôznych portov.
Typ adaptéru určuje, či adaptér podporuje zápis (XvInputMask
), čítanie (XvOutputMask
) a typy dát, ktoré môže prijímať (pixmapu - XvImageMask
, video - XvVideoMask
, alebo statický obraz - XvStillMask
).
XvAdaptorInfo *adaptors = 0; unsigned int count = 0; if (XvQueryAdaptors(getDpy(), DefaultRootWindow(getDpy()), &count, &adaptors) == Success) { for (unsigned int i = 0; i < count; ++i) { if ((adaptors[i].type & XvInputMask) && (adaptors[i].type & XvImageMask)) { qDebug() << adaptors[i].name; qDebug() << " Base ID:" << adaptors[i].base_id; qDebug() << " Ports: " << adaptors[i].num_ports; } } XFree(adaptors); }
Výstup pre grafickú kartu Intel GMA:
Intel(R) Video Overlay Base ID: 75 Ports: 1 Intel(R) Textured Video Base ID: 76 Ports: 16
Pri použití Xv musí byť port exkluzívne uzamknutý aplikáciou, ktorá ho používa. Zamykanie sa vykonáva volaním XvGrabPort
a odomykanie volaním XvUngrabPort
. Uzamykanie a odomykanie portu sa musí volať aj s časovou pečiatkou akcie (vystačíme si s použitím CurrentTime
).
XvPortID port = 75; if (XvGrabPort(getDpy(), port, CurrentTime) == Success) { ... } ... // Ukončenie používania portu XvUngrabPort(getDpy(), port, CurrentTime);
Súčasná verzia Xv podporuje 2 farebné priestory. Je to klasické RGB a YUV používané najčastejšie pri prenose obrazu. Výhodou YUV je možnosť redukcie chromatickej zložky bez príliš viditeľného zhoršenia kvality obrazu.
Zoznam podporovaných formátov je dostupný pomocou volania XvListImageFormats
. Štruktúra XvImageFormatValues
obsahuje nasledujúce položky.
Pre všetky typy | |
---|---|
id: int | Unikátny identifikátor formátu v rámci adaptéru. Tento identifikátor sa používa pri komunikácii s adaptérom. |
type: int | Typ formátu, buď XvRGB alebo XvYUV. |
byte_order: int | Spôsob zoradenia bytov: LSBFirst - najmenej významný na začiatku alebo MSBFirst - najvýznamnejší na začiatku. |
guid: char[16] | Globálny identifikátor. |
bits_per_pixel: int | Počet bitov pixelu. |
format: int | Zabalený - XvPacked (jednotlivé komponenty nasledujú v streame za sebou, napr YUYV) alebo planárny - XvPlanar (komponenty sú oddelené, napr. YYYYUUVV). |
num_planes int | Počet komponentov ak sa používa planárny formát |
Pre RGB | |
depth int | Farebná hĺbka. |
red_mask unsigned int | Maska bitov červenej farby napr. 0x00ff0000. |
green_mask unsigned int | Maska bitov zelenej farby napr. 0x0000ff00. |
blue_mask unsigned int | Maska bitov modrej farby napr. 0x000000ff. |
Pre YUV | |
y_sample_bits unsigned int | Počet bitov pre zložku Y (luminancia). |
u_sample_bits unsigned int | Počet bitov pre chromatickú zložku U. |
v_sample_bits unsigned int | Počet bitov pre chromatickú zložku V. |
horz_y_period unsigned int | Horizontálna veľkosť makrobloku Y. |
horz_u_period unsigned int | Horizontálna veľkosť makrobloku U. |
horz_v_period unsigned int | Horizontálna veľkosť makrobloku V. |
vert_y_period unsigned int | Vertikálna veľkosť makrobloku Y. |
vert_u_period unsigned int | Vertikálna veľkosť makrobloku U. |
vert_v_period unsigned int | Vertikálna veľkosť makrobloku V. |
component_order char[32] | Poradie zložiek napr. YUYV. |
scanline_order int | Poradie riadkov v streame, buď zhora nadol - XvTopToBottom alebo zdola nahor - XvBottomToTop. |
Nasledujúcim kódom sa dá vypísať zoznam podporovaných formátov.
XvImageFormatValues *formats = XvListImageFormats(getDpy(), m_port, &count); if (formats) { for (int i = 0; i < count; ++i) { qDebug() << "Format:" << formats[i].id; if (formats[i].type == XvRGB) { qDebug() << " type: RGB"; } else if (formats[i].type == XvYUV) { qDebug() << " type: YUV"; } if (formats[i].byte_order == LSBFirst) { qDebug() << " byte order: LSB"; } else if (formats[i].byte_order == MSBFirst) { qDebug() << " byte order: MSB"; } qDebug() << " bits per pixel:" << formats[i].bits_per_pixel; if (formats[i].format == XvPacked) { qDebug() << " format: Packed"; } else if (formats[i].format == XvPlanar) { qDebug() << " format: Planar"; } qDebug() << " num planes:" << formats[i].num_planes; if (formats[i].type == XvRGB) { qDebug() << " depth:" << formats[i].depth; qDebug() << " red mask:" << QString("%1").arg(formats[i].red_mask, 0, 16); qDebug() << " green mask:" << QString("%1").arg(formats[i].green_mask, 0, 16); qDebug() << " blue mask:" << QString("%1").arg(formats[i].blue_mask, 0, 16); } else if (formats[i].type == XvYUV) { qDebug() << " y sample bits:" << formats[i].y_sample_bits; qDebug() << " u sample bits:" << formats[i].u_sample_bits; qDebug() << " v sample bits:" << formats[i].v_sample_bits; qDebug() << " horz y period:" << formats[i].horz_y_period; qDebug() << " horz u period:" << formats[i].horz_u_period; qDebug() << " horz v period:" << formats[i].horz_v_period; qDebug() << " vert y period:" << formats[i].vert_y_period; qDebug() << " vert u period:" << formats[i].vert_u_period; qDebug() << " vert v period:" << formats[i].vert_v_period; qDebug() << " component order:" << formats[i].component_order; if (formats[i].scanline_order == XvTopToBottom) { qDebug() << " scanline order: TopToBottom"; } else if (formats[i].scanline_order == XvBottomToTop) { qDebug() << " scanline order: BottomToTop"; } } } XFree(formats); }
Ovládače Intel GMA podporujú nasledujúce formáty:
Format: 844715353 type: YUV byte order: LSB bits per pixel: 16 format: Packed num planes: 1 y sample bits: 8 u sample bits: 8 v sample bits: 8 horz y period: 1 horz u period: 2 horz v period: 2 vert y period: 1 vert u period: 1 vert v period: 1 component order: YUYV scanline order: TopToBottom Format: 842094169 type: YUV byte order: LSB bits per pixel: 12 format: Planar num planes: 3 y sample bits: 8 u sample bits: 8 v sample bits: 8 horz y period: 1 horz u period: 2 horz v period: 2 vert y period: 1 vert u period: 2 vert v period: 2 component order: YVU scanline order: TopToBottom Format: 808596553 type: YUV byte order: LSB bits per pixel: 12 format: Planar num planes: 3 y sample bits: 8 u sample bits: 8 v sample bits: 8 horz y period: 1 horz u period: 2 horz v period: 2 vert y period: 1 vert u period: 2 vert v period: 2 component order: YUV scanline order: TopToBottom Format: 1498831189 type: YUV byte order: LSB bits per pixel: 16 format: Packed num planes: 1 y sample bits: 8 u sample bits: 8 v sample bits: 8 horz y period: 1 horz u period: 2 horz v period: 2 vert y period: 1 vert u period: 1 vert v period: 1 component order: UYVY scanline order: TopToBottom
Pri vykresľovaní som vybral prvý formát zo zoznamu v minulej kapitole. Video buffer je umiestnený v inštancii triedy QVideoFrame
. To je v tomto prípade YUYV
. Budeme chcieť vykresliť obraz o veľkosti 400 x 300 pixelov. Vo výpise vidieť, že počet bitov na pixel je 16 tj. každý pixel je zakódovaný do 2 bytov. Nasledujúcim kódom alokujeme video buffer s read-write prístupom k bitom.
int w = 400; int h = 300; QSize s(400, 300); QVideoFrame videoFrame = QVideoFrame(w * h * 2, s, w * 2, QVideoFrame::Format_YUYV); videoFrame.map(QAbstractVideoBuffer::ReadWrite);
Prvým argumentom konštruktora QVideoFrame je veľkosť alokovaného bufferu (veľkosť obrázku vynásobená 2 pretože každý pixel je zakódovaný do práve 2 bytov). Nasleduje veľkosť obrázku, počet bytov na riadok a formát.
Následne môžme pracovať s bytmi, napr. vyplniť ich nudnou šedou farbou . Zo štruktúry YUYV je vidieť, že na každú zložku luminancie pripadá len jedná chromatická zložka. Zložky U a V sú teda spoločné pre jeden makroblok veľkosti 2 x 1 px. Preto pre každý druhý pixel nastavujeme hodnotu U a V. Pre prevod z RGB do YUV som použil jednoduchú aproximáciu.
unsigned char rgb2y(unsigned char r, unsigned char g, unsigned char b) { return ((r * 66 + g * 129 + b * 25) >> 8) + 16; } unsigned char rgb2u(unsigned char r, unsigned char g, unsigned char b) { return ((r * (-38) + g * (-74) + b * 112) >> 8) + 128; } unsigned char rgb2v(unsigned char r, unsigned char g, unsigned char b) { return ((r * 112 + g * (-94) + b * (-18)) >> 8) + 128; } ... for (size_t px = 0; px < (w * h); ++px) { videoFrame.bits()[px << 1] = rgb2y(127, 127, 127); if (px % 2 == 0) { videoFrame.bits()[(px << 1) + 1] = rgb2u(127, 127, 127); } else { videoFrame.bits()[(px << 1) + 1] = rgb2v(127, 127, 127); } }
Ostalo už len samotné vykreslenie obrazových dát. Na to budeme potrebovať vytvoriť XvImage
odkazujúci na dáta z QVideoFrame
, získať grafický kontext GC, vykresliť obrázok XvPutImage
a uvoľniť prostriedky.
.
Display *dpy = getDpy(); char *bits = reinterpret_cast<char *>(videoFrame.bits()); XvImage *image = XvCreateImage(dpy, port, format_id, bits, w, h); XGCValues xgcv; GC gc = XCreateGC(dpy, winId(), 0L, &xgcv); XvPutImage(dpy, port, winId(), gc, image, 0, 0, w, h, 0, 0, win->width(), win->height()); XFreeGC(dpy, gc); XFree(image);
Pri vytváraní inštancie XvImage musíme konvertovať uchar *
na char *
kvôli inému očakávanému typu XvCreateImage
. Port a id formátu sme získali z informácií o adaptéroch a formátoch.
Volanie XvPutImage
, má na prvý pohľad hrôzostrašný počet parametrov. V skutočnosti táto funkcia umožňuje orezanie obrazu a jeho škálovanie pri vykresľovaní. Preto posledné 4 parametre sú poloha a veľkosť vykresľovaného obrazu voči počiatočným súradniciam widgetu. Predposledné 4 parametre sú poloha a veľkosť vykresľovacieho okna pixmapy.
Pomocou atribútov sa dá nastaviť obyčajne jas, kontrast, saturácia alebo gamma. Zoznam atribútov sa dá získať volaním XvQueryPortAttributes
.
int count = 0; XvAttribute *attributes = XvQueryPortAttributes(getDpy(), m_port, &count); if (attributes) { for (int i = 0; i < count; ++i) { qDebug() << attributes[i].name; qDebug() << " min:" << attributes[i].min_value; qDebug() << " max:" << attributes[i].max_value; if (attributes[i].flags == ReadFlag) { qDebug() << " flags: Read"; } else if (attributes[i].flags == WriteFlag) { qDebug() << " flags: Write"; } else if (attributes[i].flags == (ReadFlag | WriteFlag)) { qDebug() << " flags: Read | Write"; } } XFree(attributes); }
Výstup na mojej grafickej karte vyzerá nasledovne:
XV_COLORKEY min: 0 max: 16777215 flags: Read | Write XV_BRIGHTNESS min: -128 max: 127 flags: Read | Write XV_CONTRAST min: 0 max: 255 flags: Read | Write XV_SATURATION min: 0 max: 1023 flags: Read | Write XV_PIPE min: -1 max: 1 flags: Read | Write XV_GAMMA0 min: 0 max: 16777215 flags: Read | Write XV_GAMMA1 min: 0 max: 16777215 flags: Read | Write XV_GAMMA2 min: 0 max: 16777215 flags: Read | Write XV_GAMMA3 min: 0 max: 16777215 flags: Read | Write XV_GAMMA4 min: 0 max: 16777215 flags: Read | Write XV_GAMMA5 min: 0 max: 16777215 flags: Read | Write
Hodnota atribútu sa dá získať nasledujúcim kódom:
Atom atom = XInternAtom(getDpy(), "XV_BRIGHTNESS", True); int value; XvGetPortAttribute(getDpy(), port, atom, &value);
Záhadná konštanta True hovorí funkcii, že má vrátiť chybu v prípade, že atribút neexistuje.
Nastavenie atribútu má veľmi podobný kód:
Atom atom = XInternAtom(getDpy(), "XV_BRIGHTNESS", True); XvSetPortAttribute(getDpy(), port, atom, 50);
Na tomto mieste som chcel ukázať niečo fakt praktické, ale nevyšlo Takže namiesto toho je tu môj testovací obrazec pre nastavenie atribútov Xv (pozor, funguje len s YUYV). Kód je dostupný na githube. K praktickému použitiu Xv sa hádam dostanem v budúcom blogu
Tiskni
Sdílej:
+1
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.