abclinuxu.cz AbcLinuxu.cz itbiz.cz ITBiz.cz HDmag.cz HDmag.cz abcprace.cz AbcPráce.cz
Inzerujte na AbcPráce.cz od 950 Kč
Rozšířené hledání
×
    včera 22:22 | Upozornění Ladislav Hagara | Komentářů: 2
    včera 17:44 | Nová verze

    Firma Murena představila /e/OS verze 2.0. Jde o  alternativní sestavení Androidu bez aplikací Google. Mezi novinkami je podrobnější nastavení ochrany soukromí před sledováním aplikacemi. Murena prodává několik smartphonů s předinstalovaným /e/OS (Fairphone, repasovaný Google Pixel 5).

    Fluttershy, yay! | Komentářů: 0
    včera 14:33 | Zajímavý software

    Do 30. května lze v rámci akce Warhammer Skulls 2024 získat na Steamu zdarma hru Warhammer 40,000: Gladius - Relics of War.

    Ladislav Hagara | Komentářů: 0
    včera 13:33 | Nová verze

    HelenOS (Wikipedie), tj. svobodný operační systém českého původu založený na architektuře mikrojádra, byl vydán ve verzi 0.14.1. Přehled novinek v poznámkách k vydání. Vypíchnou lze nabídku Start. Videopředstavení na YouTube.

    Ladislav Hagara | Komentářů: 2
    23.5. 23:22 | Zajímavý software

    BreadboardOS je firmware pro Raspberry Pi Pico (RP2040) umožňující s tímto MCU komunikovat pomocí řádkového rozhraní (CLI). Využívá FreeRTOS a Microshell.

    Ladislav Hagara | Komentářů: 0
    23.5. 16:55 | Nová verze

    Vývojáři KDE oznámili vydání balíku aplikací KDE Gear 24.05. Přehled novinek i s náhledy a videi v oficiálním oznámení. Do balíku se dostalo 5 nových aplikací: Audex, Accessibility Inspector, Francis, Kalm a Skladnik.

    Ladislav Hagara | Komentářů: 6
    23.5. 12:55 | Nová verze

    Byla vydána (𝕏) nová verze 18.0.0 open source webového aplikačního frameworku Angular (Wikipedie). Přehled novinek v příspěvku na blogu.

    Ladislav Hagara | Komentářů: 0
    22.5. 23:44 | Pozvánky

    V neděli 26. května lze navštívit Maker Faire Rychnov nad Kněžnou, festival plný workshopů, interaktivních činností a především nadšených a zvídavých lidí.

    Ladislav Hagara | Komentářů: 0
    22.5. 16:33 | Nová verze

    Byla vydána nová stabilní verze 3.20.0, tj. první z nové řady 3.20, minimalistické linuxové distribuce zaměřené na bezpečnost Alpine Linux (Wikipedie) postavené na standardní knihovně jazyka C musl libc a BusyBoxu. Z novinek lze vypíchnou počáteční podporu 64bitové architektury RISC-V.

    Ladislav Hagara | Komentářů: 0
    22.5. 14:11 | IT novinky

    Společnost Jolla na akci s názvem Jolla Love Day 2 - The Jolla comeback představila telefon se Sailfish OS 5.0 Jolla Community Phone (ve spolupráci se společností Reeder) a počítač Jolla Mind2 Community Edition AI Computer.

    Ladislav Hagara | Komentářů: 18
    Podle hypotézy Mrtvý Internet mj. tvoří většinu online interakcí boti.
     (82%)
     (4%)
     (7%)
     (7%)
    Celkem 524 hlasů
     Komentářů: 16, poslední 14.5. 11:05
    Rozcestník

    Jaderné noviny - Video4Linux2 - 6b (streamovaný I/O)

    8. 8. 2007 | Robert Krátký | Jaderné noviny | 2893×

    Předchozí díl seriálu popisoval přenos video snímků pomocí systémových volání read() a write(). Taková implementace zvládne základní práci, ale obyčejně to není preferovaný způsob provádění video I/O. Nejvyššího výkonu a nejlepšího přenosu informací dosáhne ovladač, který podporuje V4L2 API pro streamovaný I/O.

    Obsah

    Při použití metod read() a write() je v rámci I/O operace každý snímek kopírován mezi uživatelským a jaderným prostorem. Když se používá streamovaný I/O, tak kopírování neprobíhá; místo toho si aplikace a ovladače vyměňují ukazatele na buffery. Tyto buffery jsou mapovány do adresního prostoru aplikace, takže je možné provádět snímkové I/O bez jakéhokoliv kopírování. Jsou dva druhy bufferů pro streamované I/O:

    • Memory-mapped buffery (typ V4L2_MEMORY_MMAP) jsou alokovány v jaderném prostoru; aplikace je do svého adresního prostoru namapuje systémovým voláním mmap(). Buffery mohou být velké a souvislé DMA buffery, virtuální buffery vytvořené pomocí vmalloc() nebo, pokud to hardware podporuje, mohou být umístěny přímo v I/O paměti video zařízení.

    • Uživatelské buffery (V4L2_MEMORY_USERPTR) jsou aplikací alokovány v uživatelském prostoru. V takové situaci samozřejmě není potřeba žádné volání mmap(), ale ovladač se možná bude muset více snažit, aby do uživatelských bufferů zvládl efektivní I/O.

    Poznámka: ovladače nemusejí streamované I/O podporovat. A když už ho podporují, tak nemusejí umět oba typy bufferů. Pružnější ovladač bude podporovat více aplikací; v praxi to vypadá, že většina aplikací používá memory-mapped buffery. Není možné používat oba typy najednou.

    Teď se ponoříme do množství tajemných detailů podpory streamovaného I/O. Každý programátor Video4Linux2 ovladačů bude muset tomuto API rozumět. Stojí však za zmínku, že existuje API vyšší úrovně, které může s psaním streamovacích ovladačů pomoci. Tato vrstva (nazývaná video-buf) může život usnadnit, pokud zařízení zvládá scatter/gather I/O. API video-buf bude probráno v některém z dalších dílů.

    Ovladače, které streamovaný I/O podporují, by o tom měly aplikaci informovat nastavením příznaku V4L2_CAP_STREAMING v metodě vidioc_querycap(). Nedá se však popsat, které typy bufferů jsou podporovány; to přijde na řadu až později.

    Struktura v4l2_buffer

    link

    Když je aktivován streamovaný I/O, předávají se snímky mezi aplikací a ovladačem ve formě struct v4l2_buffer. Tato struktura je komplikovaná potvora, takže její popis chvilku potrvá. Je vhodné začít zmínkou o tom, že existují tři základní stavy bufferů:

    • V příchozí frontě ovladače. Buffery jsou do této fronty umístěny aplikací, protože očekává, že s nimi ovladač provede něco užitečného. U zařízení pro zachytávání videa budou buffery v příchozí frontě prázdné; budou čekat, až je ovladač zaplní video daty. U výstupního zařízení budou v bufferech snímková data čekající na odeslání do zařízení.

    • V odchozí frontě ovladače. Tyto buffery už byly ovladačem zpracovány a čekají, až si o ně řekne aplikace. U zachytávacích zařízení budou mít odchozí buffery nová snímková data; u výstupních zařízení jsou buffery prázdné.

    • V žádné frontě. V tomto stavu patří buffer uživatelskému prostoru a ovladač se ho za normálních okolností nedotkne. Je to jediná chvíle, kdy by s bufferem měla něco dělat aplikace. Budeme tomu říkat "uživatelský" stav.

    Tyto stavy a operace, které zařizují přechody mezi nimi, jsou znázorněny na diagramu:

    Video4Linux2: Buffer states

    Struktura v4l2_buffer vypadá takto:

        struct v4l2_buffer
        {
    	__u32			index;
    	enum v4l2_buf_type      type;
    	__u32			bytesused;
    	__u32			flags;
    	enum v4l2_field		field;
    	struct timeval		timestamp;
    	struct v4l2_timecode	timecode;
    	__u32			sequence;
    
    	/* memory location */
    	enum v4l2_memory        memory;
    	union {
    		__u32           offset;
    		unsigned long   userptr;
    	} m;
    	__u32			length;
    	__u32			input;
    	__u32			reserved;
        };
    

    Pole index je sekvenční číslo identifikující buffer; používá se jen u memory-mapped bufferů. Stejně jako ostatní číslovatelné objekty v rozhraní V4L2, i memory-mapped buffery začínají na indexu 0 a pak sekvenčně stoupají. Pole type popisuje typ bufferu, většinou V4L2_BUF_TYPE_VIDEO_CAPTURE nebo V4L2_BUF_TYPE_VIDEO_OUTPUT.

    Velikost bufferu udává v bajtech length. Velikost obrazových dat v bufferu je v bytesused; je zřejmé, že bytesused <= length. U zachytávacích zařízení nastaví bytesused ovladač; u výstupních zařízení musí pole nastavit aplikace.

    field popisuje, které pole obrázku je v bufferu uloženo; o polích se mluvilo v části 5a.

    Pole timestamp u vstupních zařízení říká, kdy byl snímek zachycen. U výstupních by ovladač neměl snímek odeslat před časem uvedeným v tomto poli; timestamp nula znamená "co nejrychleji". Ovladač nastaví timestamp na čas, kdy byl na zařízení přenesen první bajt snímku - nebo co nejblíže tomuto času. timecode lze použít k uložení hodnoty timecode, což je užitečné pro aplikace na editaci videa; vizte tabulku ve specifikaci V4L2 s podrobnostmi o timecodech.

    Ovladač zaznamenává počet snímků, které přes zařízení prošly; aktuální sekvenční číslo je při přenesení každého snímku uloženo v sequence. U vstupních zařízení může aplikace toto pole sledovat, chce-li vědět o vypuštěných [dropped] snímcích.

    memory říká, jestli je buffer memory-mapped nebo uživatelský. U memory-mapped bufferů popisuje m.offset umístění bufferu. Specifikace to definuje jako offset bufferu od začátku paměti zařízení, ale pravda je taková, že je to obyčejný kouzelný cookie, který může aplikace předat funkci mmap(), aby specifikovala, který buffer je mapován. U uživatelských bufferů je m.userptr adresa bufferu v uživatelském prostoru.

    Pole input může být použito pro rychlé přepínání mezi vstupy na zachytávacím zařízení - za předpokladu, že zařízení podporuje rychlé přepínání mezi snímky. Pole reserved by mělo být nastaveno na nulu.

    A nakonec je definováno několik flags:

    • V4L2_BUF_FLAG_MAPPED značí, že byl buffer namapován do uživatelského prostoru. Týká se pouze memory-mapped bufferů.

    • V4L2_BUF_FLAG_QUEUED: buffer je v příchozí frontě ovladače.

    • V4L2_BUF_FLAG_DONE: buffer je v odchozí frontě ovladače.

    • V4L2_BUF_FLAG_KEYFRAME: buffer obsahuje klíčový snímek - užitečné u komprimovaných streamů.

    • V4L2_BUF_FLAG_PFRAME a V4L2_BUF_FLAG_BFRAME se také používají s komprimovanými streamy; označují předpokládané nebo rozdílové snímky.

    • V4L2_BUF_FLAG_TIMECODE: pole timecode je validní.

    • V4L2_BUF_FLAG_INPUT: pole input je validní.

    Nastavení bufferu

    link

    Jakmile streamovací aplikace provede své základní nastavení, obrátí pozornost k organizaci svých I/O bufferů. Prvním krokem je, s pomocí VIDIOC_REQBUFS ioctl(), které je vrstvou V4L2 změněno na volání ovladačové metody vidioc_reqbufs(), založit sadu bufferů:

        int (*vidioc_reqbufs) (struct file *file, void *private_data, 
    			   struct v4l2_requestbuffers *req);
    

    Všechny zajímavé informace budou ve struktuře v4l2_requestbuffers, která vypadá takto:

        struct v4l2_requestbuffers
        {
    	__u32			count;
    	enum v4l2_buf_type      type;
    	enum v4l2_memory        memory;
    	__u32			reserved[2];
        };
    

    Pole type popisuje druh I/O, který má být proveden; většinou to bude buď V4L2_BUF_TYPE_VIDEO_CAPTURE pro zařízení na získávání videa nebo V4L2_BUF_TYPE_VIDEO_OUTPUT pro výstupní zařízení. Jsou ještě další druhy, ale to už je mimo záběr tohoto textu.

    Pokud chce aplikace používat memory-mapped buffery, nastaví memory na V4L2_MEMORY_MMAP a count na počet bufferů, které chce. Nepodporuje-li ovladač memory-mapped buffery, měl by vrátit -EINVAL. Jinak by měl interně alokovat požadované buffery a vrátit nulu. Při návratu bude aplikace čekávat připravené buffery, takže všechny části procesu, které by mohly selhat (například alokace paměti), by měly být provedeny právě v tuto chvíli.

    Ovladač však nemusí alokovat přesně tolik bufferů, kolik bylo požadováno. V mnoha případech existuje minimální počet bufferů, který dává smysl; pokud si aplikace vyžádá méně než dané minimum, může jich dostat více, než o kolik si řekla. Například aplikace MPlayer si žádá dva buffery, takže má sklony přesahovat [overrun] (a tím pádem ztrácet snímky), když dojde ke zpomalení uživatelského prostoru. Vynucením většího počtu bufferů (lze změnit parametrem modulu) zařídí ovladač cafe_ccic trochu stabilnější streamování. Než metoda skončí, mělo by být pole count nastaveno na počet bufferů, které byly nakonec alokovány.

    Nastavení count na nulu je způsob, kterým může aplikace požadovat uvolnění všech existujících bufferů. V takovém případě musí ovladač zastavit veškeré DMA operace, než začne buffery uvolňovat, nebo se stanou hrozné věci. Také není možné uvolnit buffery aktuálně mapované do uživatelského prostoru.

    Pokud mají být použity uživatelské buffery, záleží pouze na poli type (bufferu) a hodnotě V4L2_MEMORY_USERPTR v poli memory. Aplikace nemusí specifikovat počet bufferů, které chce použít; protože bude alokace probíhat v uživatelském prostoru, ovladač se o ni nemusí zajímat. Pokud ovladač uživatelské buffery podporuje, musí vzít pouze na vědomí, že aplikace bude tuto vlastnost používat, a vrátit nulu; jinak je potřeba obvyklé -EINVAL.

    Příkaz VIDIOC_REQBUFS je jediný způsob, jak může aplikace zjistit, které typy streamovacích I/O bufferů daný ovladač podporuje.

    Mapování bufferů do uživatelského prostoru

    link

    Pokud jsou používány uživatelské buffery, nedostanou se k ovladači žádná další volání týkající se bufferů, dokud nezačne aplikace buffery vkládat do příchozí fronty. Memory-mapped buffery však vyžadují náročnější přípravu. Aplikace většinou projde všechny alokované buffery a namapuje je do svého adresního prostoru. První zastávkou je příkaz VIDIOC_QUERYBUF, ze kterého se stane volání ovladačové metody vidioc_querybuf():

        int (*vidioc_querybuf)(struct file *file, void *private_data, 
                               struct v4l2_buffer *buf);
    

    Při vstupu do této metody budou z buf nastavena pouze pole type (které by mělo být zkontrolováno - porovnáno s druhem specifikovaným při alokaci bufferu) a index (které specifický buffer identifikuje). Ovladač by si měl pohlídat, aby index dávalo smysl, a vyplnit zbývající pole v buf. Ovladače většinou ukládají pole struktur v4l2_buffer interně, takže hlavní funkcí metody vidioc_querybuf() je přiřazení struktury.

    Jediný způsob, jak může aplikace přistupovat k memory-mapped bufferům, je mapovat je do jejich adresního prostoru. Takže volání vidioc_querybuf() bude obyčejně následováno voláním ovladačové metody mmap() - ta je uložena v poli fops struktury video_device přiřazené k danému zařízení. Jak ovladač pracuje s mmap(), to záleží na tom, jak jsou buffery v jádře nastaveny. Pokud může být buffer namapován dopředu pomocí remap_pfn_range() nebo remap_vmalloc_range(), mělo by to být provedeno právě v tuto chvíli. Pro buffery v jaderném prostoru mohou být stránky mapovány individuálně v okamžiku výpadku stránky [page-fault time] pomocí metody nopage(). Dobrý popis práce s mmap() je v knize Linux Device Drivers.

    Když je zavolána mmap(), měla by mít předávaná VMA struktura adresu jednoho z vašich bufferů v poli vm_pgoff - samozřejmě posunuto doprava pomocí PAGE_SHIFT. Měla by to být hodnota offset, kterou ovladač vrátil v reakci na volání VIDIOC_QUERYBUF. Projděte prosím všechny buffery a ujistěte se, že příchozí adresa odpovídá jednomu z nich; video ovladače by neměly sloužit jako prostředek pro nepřátelské programy k mapování libovolných oblastí paměti.

    Hodnota offset, kterou poskytnete, může být téměř libovolná. Některé ovladače prostě vracejí (index<<PAGE_SHIFT), což znamená, že příchozí pole vm_pgoff by mělo být stejné jako index bufferu. Jediná věc, kterou byste dělat neměli, je ukládat do offset skutečnou jadernou adresu bufferu; pouštění jaderných adres do uživatelského prostoru nikdy není dobrý nápad.

    Když uživatelských prostor mapuje buffer, měl by ovladač nastavit příznak V4L2_BUF_FLAG_MAPPED v příslušné struktuře v4l2_buffer. Také musí připravit VMA operace open() a close(), aby mohl sledovat počet procesů, které mají buffer namapovaný. Dokud zůstává buffer někde mapovaný, nemůže být uvolněn zpět do jádra. Pokud klesne počet mapování u jednoho nebo více bufferů na nulu, měl by ovladač zastavit veškeré probíhající I/O, protože nebude k dispozici žádný proces, který by to mohl využít.

    Streamovaný I/O

    link

    Prozatím jsme probírali spoustu nastavení, aniž by byl přenesen jediný snímek. Už se k tomu blížíme, ale ještě jednu věc je potřeba udělat. Když aplikace získá buffery pomocí VIDIOC_REQBUFS, budou všechny tyto buffery v uživatelském stavu; pokud jsou to uživatelské buffery, tak ve skutečnosti ještě neexistují. Než může aplikace začít streamovaný I/O, musí umístit alespoň jeden buffer do příchozí fronty ovladače; u výstupního zařízení by tyto buffery měly být samozřejmě naplněny validními snímkovými daty.

    Pro zařazení bufferu do fronty zavolá aplikace ioctl() VIDIOC_QBUF, které V4L2 namapuje do volání ovladačové metody vidioc_qbuf():

        int (*vidioc_qbuf) (struct file *file, void *private_data, 
                            struct v4l2_buffer *buf);
    

    U memory-mapped bufferů jsou platná pouze pole type a index ve struktuře buf. Ovladač může provést různé kontroly (type a index dávají smysl, buffer ještě není v jedné z front ovladače, buffer je mapovaný atd.), dát buffer do příchozí fronty (nastavit příznak V4L2_BUF_FLAG_QUEUED) a skončit.

    Uživatelské buffery mohou být v tento moment trochu komplikovanější, protože ovladač ještě takový buffer nikdy neviděl. Když se používá tato metoda, smějí aplikace při každém zařazování bufferu do fronty předat jinou adresu, takže se ovladač nemůže předem připravit. Pokud váš ovladač tlačí snímky přes buffer v jaderném prostoru, musí si jen od aplikace zaznamenat adresu v uživatelském prostoru. Jestliže se však snažíte dostat data prostřednictvím DMA přímo do uživatelského prostoru, budete to mít o dost těžší.

    Aby mohl ovladač posílat data přímo do uživatelského prostoru, musí nejprve "fault in" [zařídit výpadky] všechny stránky bufferu a zamknout je na místě; dělá to nástroj get_user_pages(). Nezapomeňte, že tato funkce může provádět alokace paměti a diskový I/O ve velkém objemu, takže může na dlouho blokovat. Musíte se postarat o to, aby důležité funkce ovladače nezamrzly, zatímco get_user_pages() dělá svou práci - může blokovat dost dlouho na to, aby uteklo hodně video snímků.

    Pak potřebujeme zařízení říct, aby přeneslo obrázková data do (nebo z) uživatelského bufferu. Tento buffer nebude ve fyzické paměti souvislý - místo toho bude rozdělen na velký počet samostatných stránek o 4096 bajtech (na většině architektur). Je zřejmé, že zařízení musí zvládat scatter/gather DMA operace. Pokud zařízení přenáší celé video snímky najednou, bude muset přijmout scatterlist, který uchová spoustu stránek; obrázek v rozlišení VGA v 16bitovém formátu potřebuje 150 stránek. S rostoucí velikostí obrázku se zvětšuje i scatterlist. Specifikace V4L2 říká:

    Pokud to vyžaduje hardware, swapuje ovladač stránky paměti v rámci fyzické paměti, aby vytvořil souvislou oblast. To se, z hlediska aplikace, děje transparentně v jaderném subsystému virtuální paměti.

    Nedoporučoval bych však [Jonathan Corbet], aby autoři ovladačů zkoušeli takové kejkle s virtuální pamětí. Rozumnější by byl požadavek, aby byly uživatelské buffery umístěny v hugetlb stránkách, ale to zatím žádné ovladače nedělají.

    Pokud vaše zařízení přenáší obrázky po menších kusech (například USB foťák), může být příprava přímého DMA do uživatelského prostoru snazší. Když přijde na rozhodování, jestli podporovat přímý I/O do uživatelských bufferů, měl by si autor ovladače ujasnit, že 1) to za to stojí - s ohledem na aplikace, které stejně většinou očekávají memory-mapped buffery, a 2) je lepší k tomu využít vrstvu video-buf, která se může o některé nepříjemnosti postarat za vás.

    Jakmile začne streamovaný I/O, vezme ovladač buffer ze své příchozí fronty, nechá zařízení provést požadovaný přenos a pak buffer přesune do odchozí fronty. Příznaky bufferu by měly být odpovídajícím způsobem upraveny, když dojde k tomuto přechodu; také by v tu chvíli měla být vyplněna pole jako sekvenční číslo a časový otisk [timestamp]. Nakonec bude z odchozí fronty chtít buffery aplikace, aby je vrátila do uživatelského stavu. To je práce pro VIDIOC_DQBUF, ze které se stane toto volání:

        int (*vidioc_dqbuf) (struct file *file, void *private_data, 
                             struct v4l2_buffer *buf);
    

    Ovladač odstraní první buffer z odchozí fronty a relevantní informace uloží do *buf. Pokud je odchozí fronta prázdná, tak by se, za normálních okolností, mělo toto volání zablokovat, dokud nebude nějaký buffer k dispozici. Od V4L2 ovladačů se však očekává, že budou zvládat neblokovaný I/O, takže pokud bylo video zařízení otevřeno pomocí O_NONBLOCK, ovladač by měl při prázdné frontě vrátit -EAGAIN. Netřeba připomínat, že tento požadavek zároveň u streamovaného I/O znamená povinnou podporu poll().

    Už zbývá jen zařízení říci, aby začalo provádět streamovaný I/O. Ovladače Video4Linux2 mají pro tento úkol následující metody:

        int (*vidioc_streamon) (struct file *file, void *private_data, 
                                enum v4l2_buf_type type);
        int (*vidioc_streamoff)(struct file *file, void *private_data, 
        	                    enum v4l2_buf_type type);
    

    Volání vidioc_streamon() by mělo zařízení nastartovat (po kontrole, jestli type dává smysl). Je-li to potřeba, může ovladač požadovat, aby byl v příchozí frontě určitý počet bufferů, než začne streamování.

    Jakmile aplikace skončí, měla by spustit volání vidioc_streamoff(), které musí zařízení zastavit. Ovladač by měl také z příchozí i odchozí fronty odstranit všechny buffery a ponechat je v uživatelském stavu. Ovladač však musí být pochopitelně připraven i na to, že by aplikace mohla zařízení vypnout bez ukončení streamování.

           

    Hodnocení: 100 %

            špatnédobré        

    Nástroje: Tisk bez diskuse

    Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

    Komentáře

    Vložit další komentář

    andree avatar 8.8.2007 22:44 andree | skóre: 39 | blog: andreeeeelog
    Rozbalit Rozbalit vše Re: Jaderné noviny - Video4Linux2 - 6b (streamovaný I/O)
    uf, tento v4l2 serial pomaly vyda na kapitolku v nejakej knihe "programovani pro linux", mam pocit :-)
    9.8.2007 14:50 xkennyx
    Rozbalit Rozbalit vše Re: Jaderné noviny - Video4Linux2 - 6b (streamovaný I/O)
    Zdravim, moc dekuju za clanky o V4L2. Pisu ovladac pro kamerku a nektere veci mi nebyly uplne jasne ani po precteni dokumentace :) Ted uz zacinam chapat a udelam revizi kodu :)

    Super clanek :) S pozdravem Jarek
    ISSN 1214-1267   www.czech-server.cz
    © 1999-2015 Nitemedia s. r. o. Všechna práva vyhrazena.