┌vod
Instalace Ogg SDK
Torie k DirectSound
╚ßst 1
╚ßst 2
╚ßst 3
╚ßst 4
Funkce knihovny vorbisfile.dll
Odkazy
Ke sta₧enφ

R.Henys@seznam.cz
ICQ: 159629842

Poslednφ Φßst. NapφÜeme funkci CreateOggCodecObject pro poskytovßnφ ukazatel∙ na rozhranφ kterß poskytujek knihovna OggCodec kterou jsme napsali v p°edchozφch Φßstech. Ve druhΘ Φßsti se pokusφme p°ehrßt testovacφ soubor v projektu Tester.

Obsah:

4.1 Funkce CreateOggCodecObject

Ve t°etφ Φßsti jste si jist∞ vÜimli funkce CreateOggCodecObject. Pou₧ili jsme ji k zφskßnφ ukazatele na rozhranφ IOggFile. Nynφ se jφ podφvßme pod kapotu. Op∞t se jednß o funkci kterß u₧ byla probφrßna v kurzu Direct X. ZaΦneme hlaviΦkov²m souborem s definicφ. Manager.h:


    #pragma once

    enum OGGCODEC_API OGGIID
    {
       OGGIID_ISoundManager,
       OGGIID_IOggFile
    };

    OGGCODEC_API HRESULT CreateOggCodecObject(OGGIID IntrfaceID,void** ppv);

Jeden z nejkratÜφch soubor∙ celΘho °eÜenφ :) Nejd°φve definujeme v²Φtov² datov² typ s konstantami pro r∙znß rozhranφ je₧ poskytuje naÜe knihovna OggCodec. V naÜem p°φpad∞ je to jen IOggFile a ISoundManager. DalÜφ je prototyp funkce CreateOggCodecObject. Je exportovanß z knihovny stejn∞ jako zmφn∞nß rozhranφ. Prvnφm parametrem je konstanta urΦujφcφ rozhranφ na kterΘ chceme zφskat ukazatel. Druh² parametr je ukazatel na prom∞nnou do kterΘ se mß ulo₧it ukazatel na rozhranφ. (P°φÜernß v∞ta :)). Te∩ implementace, soubor Manager.cpp:

    #include "StdAfx.h"
    #include "Common.h"
    #include "Interfaces.h"
    #include "OggFile.h"
    #include "SoundManager.h"
    #include "Manager.h"

    OGGCODEC_API HRESULT CreateOggCodecObject(OGGIID InterfaceID,void** ppv)
    {
       static ISoundManager* g_SoundManager = NULL;

       switch (InterfaceID)
       {
       case OGGIID_ISoundManager:
          if (!g_SoundManager)
             *ppv = g_SoundManager = new CSoundManager;
          else
             *ppv = g_SoundManager;
          ((ISoundManager*)(*ppv))->AddRef();
          break;
       case OGGIID_IOggFile:
          *ppv = new COggFile;
          ((IOggFile*)(*ppv))->AddRef();
          break;
       default:
          return E_NOINTERFACE;
       }

       return S_OK;
    }

Ve funkci se nachßzφ statickß prom∞nnß typu ukazatel na ISoundManager. To je jen kv∙li tomu, aby v₧dy kdy₧ si za₧ßdßme o ukazatel na rozhranφ ISoundManager byl vrßcen stejn² ukazatel. Tj. t°φda CSoundManager bude vytvo°ena jen p°i prvnφm volßnφ. Proto₧e je CSoundManager potomek ISoundManager, m∙₧eme vrßtit ukazatel na ISoundManager, kterΘ poskytuje jen metody. P°i po₧adavku na rozhranφ IOggFile se vytvß°φ v₧dy novß t°φda COggFile a je vrßcen ukazatel na IOggFile. Pro ob∞ rozhranφ se p°ed nßvratem z funkce volß funkce AddRef, kterß zv²Üφ poΦet odkaz∙ na t°φdu. Snad jsem to moc nezamotal. Pro zdßrnΘ pochopenφ rozhranφ je nutnΘ znßt d∞diΦnost. Probφrala se v kurzu C++ od 13. dφlu.

Obsah

4.2 Projekt Tester

Zb²vß nßm snad u₧ jen pou₧φt vytvo°enß rozhranφ v projektu Tester a p°ehrßt si n∞jak² ten soubor. V²vojovΘ prost°edφ za nßs vytvo°ilo zßkladnφ oeknnφ aplikaci pro Windows. Nemusφme se tedy zab²vat vytvß°enφm ok²nka, ale jen dopsßnφm naÜeho k≤du a modifikacφ toho co vygeneroval pr∙vodce. N∞kterΘ Φßsti nßm toi₧ nevyhovujφ. Nejd°φve se podφvjeme na hlaviΦkovΘ soubory vlo₧enΘ do souboru Tester.cpp

    #include "stdafx.h"
    #include "Tester.h"
    #include "../oggcodec/common.h"
    #include "../oggcodec/interfaces.h"
    #include "../oggcodec/manager.h"

Soubory stdafx.h a Tester.h za nßs vlo₧ilo ji₧ v²vojovΘ prost°edφ. Zb²vajφcφ t°i musφme vlo₧it my. V soubor Common.h jsou spoleΦnΘ definicie kterΘ vy₧aduje veÜker² k≤d projektu. Intrfaces.h obsahuje definice rozhranφ a Manager.h obsahuje definici funkce CreateOggCodecObject. Jedna malß ·prava kterß nßs Φekß je vymazßnφ prom∞nnΘ HWND hWnd z t∞la funkce InitInstance. Vyma₧te jen definici. Pot°ebujeme mφt tuto prom∞nnou globßlnφ a ne aby si ji syslila jedna funkce pro sebe. Nadefinujte ji tedy jako globßlnφ prom∞nnou na zaΦßtku souboru p°ed implemtacφ vÜech funkcφ a za vlo₧en²mi hlaviΦkov²mi soubory. Kdy₧ u₧ jsme u t∞ch globßlnφch prom∞nn²ch, definujte jeÜt∞ jednu typu ukazatel na ISoundManager (ISoundManager* gSoundManager = NULL;). Te∩ se podφvßme na funkci _tWinMain, kterß obsahuje hlavnφ programovou smyΦku.

    int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow)
    {
    // TODO: Place code here.
       MSG msg;
       HACCEL hAccelTable;

       // Initialize global strings
       LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
       LoadString(hInstance, IDC_TESTER, szWindowClass, MAX_LOADSTRING);
       MyRegisterClass(hInstance);

       // Perform application initialization:
       if (!InitInstance (hInstance, nCmdShow))
       {
          return FALSE;
       }

       hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_TESTER);

       HRESULT hRet = CreateOggCodecObject(OGGIID_ISoundManager,(void**)&gSoundManager);
       if (FAILED(hRet))
          return hRet;

       hRet = gSoundManager->CreateDirectSoundSystem(hWnd);
       if (FAILED(hRet))
          return hRet;

       hRet = gSoundManager->LoadOgg(0,"test.ogg");

       IOggFile* gOgg = gSoundManager->GetOggFile(0);
       if (gOgg)
       {
          FILE* fstream = fopen("ogg_info.txt","w");
          fprintf(fstream,"Info about file: test.ogg\n");
          fprintf(fstream,"File Vendor: %s\n",gOgg->GetOggFileVendor());
          fprintf(fstream,"File Version: %d\n",gOgg->GetOggFileVersion());
          fprintf(fstream,"File Playing Time: %g\n",gOgg->GetOggFilePlayingTime());
          fprintf(fstream,"File PCM Size: %ld\n",gOgg->GetOggFilePCMSize());

          for (UINT i = 0; i < gOgg->GetCountOfOggFileComments(); i++)
             fprintf(fstream,"Comment %d, %s\n",i,gOgg->GetOggFileComment(i));

          fclose(fstream);
       }

       gOgg->ConvertOggToWav("test.ogg","test.wav");
       hRet = gSoundManager->PlayOgg(0);

       // Main message loop:
       while(true)
       {
        // Look for messages, if none are found then
       // update the state and display it

          if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
          {
             if( 0 == GetMessage(&msg, NULL, 0, 0 ) )
             {
                // WM_QUIT was posted, so exit
                SAFE_RELEASE(gSoundManager);
                return (int)msg.wParam;
             }

             // Translate and dispatch the message
             TranslateMessage( &msg );
             DispatchMessage( &msg );
          }
          else
          {
             hRet = gSoundManager->CheckAllOggIfNeedReFillBuffer();
          }
       }

       SAFE_RELEASE(gSoundManager);
       return (int) msg.wParam;

    }

K≤d vyznaΦen² tuΦn∞ je ten kter² jsem p°idal nebo upravil. Upravenß je hlavnφ programovß smyΦka (cyklus while). Zbytek je k≤d generovan² p°i vytvß°enφ projektu. ZaΦneme tφm ₧e po₧ßdßme funkci CreateOggCodecObject o ukazatel na ISoundManager stejn∞ jako jsme to d∞lali v p°φpad∞ rozhranφ IOggFile. Jen prvnφ parametr je OGGIID_ISoundManager. Mßme ukazatel na rozhranφ ISoundManager, ale jeÜt∞ ne₧ zaΦneme volat funkce pro prßci s ogg, m∞li bychom inicializovat DirectSound. Zavolßme funkci CreateDirectSoundSystem rozhranφ ISoundManager. Zadßvßme jen prvnφ parametr, handle okna (globßlnφ prom∞nnß hWnd). Druh² parametr, m≤d spoluprßce je nepovinn² (mß v²chozφ hodnotu DSSCL_PRIORITY). Pokud se to poda°φ, m∙₧eme se pokusit naΦφst ogg soubor. Jß v tomto p°φkladu pou₧φvßm pevn² nßzev test.ogg, ale nenφ problΘm brßt jmΘno souboru nap°φklad z p°φkazovΘ °ßdky. Otev°eme soubor test.ogg pomocφ funkce LoadOgg. M∞li bychom v₧dy kontrolovat vrßcenou hodnotu, zda nedoÜlo k chyb∞. Zde to nemßm jen z toho d∙vodu ₧e soubor je zadßn na pevno a p°i testovßnφ k ₧ßdnΘ chyb∞ nedoÜlo. Pokud ale budete d∞lat univerzßlnφ p°ehrßvaΦ, v₧dy kontrolujte vrßcenou hodnotu. Platφ to dvojnßsob hlavn∞ p°i programovßnφ s DirectX. Tedy tam jsem to pocφtil nejvφc. V∞tÜina k≤du je jen kontrola vrßcen²ch hodnot a p°edchßzenφ chybßm. Dßle se v k≤du ptßme na ukazatel rozhranφ IOggFile prßv∞ otev°enΘho test.ogg. Nßsledn∞ toto rozhranφ pou₧φvßme k p°φstupu k funkcφm kterΘ rozhranφ ISoundManager neposkytuje a pou₧φvßme je k v²pisu zßkladnφch informacφ o souboru do souboru ogg_info.txt. PotΘ testujeme funkci ConvertOggToWav. O jejφ funkΦnosti by vßs m∞l p°esv∞dΦit dek≤dovan² soubor na disku. Poslednφm p°φkazem p°ed hlavnφ programovou smyΦkou je spuÜt∞nφ p°ehrßvßnφ pomocφ funkce PlayOgg. V hlavnφ programovΘ smyΦce se k≤d v∞nuje hlavn∞ zpracovßnφ zprßv a pokud zrovna ₧ßdnΘ nejsou (netahßte oknem z mφsta na mφsto nebo na n∞j neklikßme jako divφ) volß funkci CheckAllOggIfNeedReFillBuffer aby bylo zajiÜt∞no p°ehrßvßnφ souboru. Pokud dojde k ukonΦenφ smyΦky, tj. aplikace je ukonΦena, nesmφme zapomenout uvolnit rozhranφ pomocφ makra SAFE_RELEASE. Poslednφ ·pravy jsou ve funkci WndProc:

    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
       int wmId, wmEvent;
       PAINTSTRUCT ps;
       HDC hdc;

       switch (message)
       {
       case WM_ACTIVATEAPP:
          //zjisteni zda je aplikace aktivni, pokud je, prijde zprava WM_ACTIVATE
          IsAppActive = (bool)wParam;
          if (gSoundManager)
             gSoundManager->HandleAppActiveStateChanges(IsAppActive);

          break;

       case WM_COMMAND:
          wmId = LOWORD(wParam);
          wmEvent = HIWORD(wParam);
          // Parse the menu selections:
          switch (wmId)
          {
          case IDM_ABOUT:
             DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
             break;
          case IDM_EXIT:
             DestroyWindow(hWnd);
             break;
          default:
             return DefWindowProc(hWnd, message, wParam, lParam);
          }
       break;
       case WM_PAINT:
          hdc = BeginPaint(hWnd, &ps);
          // TODO: Add any drawing code here...
          EndPaint(hWnd, &ps);
          break;
       case WM_DESTROY:
          gSoundManager->StopAllOgg();
          PostQuitMessage(0);
          break;
       default:
          return DefWindowProc(hWnd, message, wParam, lParam);
       }
       return 0;
    }

Zde jsem p°idal jen reakci na zprßvu WM_ACTIVATEAPP a zastavenφ p°ehrßvßnφ vÜech soubor∙ (StopAllOgg) p°i zprßv∞ WM_DESTROY (aplikace je ukonΦovßna). Pokud obdr₧φme zprßvu WM_ACTIVATEAPP, zavolßme funkci HandleAppActiveStateChanges kterß pozastavφ nebo op∞t spustφ p°ehrßvßnφ soubor∙. To je z toho d∙bodu, ₧e kdy₧ ztratφme fokus, nemusφ se stihnout volat funkce pro obnovenφ zvukovΘho bufferu v dostateΦn²ch intervalech. Mohly by vznikat mezery, slyÜeli bychom n∞jakou Φßst vφcekrßt ne₧ by doÜlo k dalÜφmu obnovenφ. To je vÜe. Pokud nynφ sestavφte celΘ °eÜenφ ... co₧ se vßm nepoda°φ, ne ₧e by bylo n∞co Üpatn∞, jen z principu, p°eklepy, tu zapomenete vlo₧it hlaviΦkov² soubor atd. M∞ se snad jeÜt∞ nikdy nepoda°ilo sestavit p°φklad z n∞jakΘho chytrΘho v²ukovΘho kurzu napoprvΘ :) A₧ se vßm to poda°φ, m∞l by se soubor p°ehrßvat korektn∞. (Ale nevφm, n∞kterΘ moje programy majφ tendenci fungovat jen m∞ a jinde mi d∞lat ostudu :(). Ale...

Obsah

4.3 ┌myslnß chyba v COggFile

Bohu₧el to nenφ p°eklep ani aprφl, ve t°φd∞ COggFile mßme chybu, a o docela vß₧nou. Ale jak °φkajφ n∞kte°φ z m²ch vyuΦujφcφch na vysokΘ Ükole, ka₧dß chyba kterou ud∞lßm, je Φist∞ ·myslnß aby jste se z nφ pouΦili a abych v∞d∞l zda dßvßte pozor a vÜimnete si jφ. Ne te∩ vß₧ne, samoz°ejm∞ ₧e to nebyla ·myslnß chyba. NaÜel jsem ji kdy₧ jsem dola∩oval ukßzkov² projekt pro tento kurz, ale °ekl jsem si ₧e mo₧nß bude lepÜφ ne₧ opravovat druhou lekci ji tam nechat a vrßtit se k nφ a₧ tady, abychom se z nφ mohli pouΦit a vid∞li ₧e nikdo nenφ neomyln². Doufßm ₧e to byla jedna z poslednφch. Jde o to, ₧e mßme Üpatn∞ naprogramovanou obnovu bufferu. P°esn∞ji °eΦeno, zapom∞l jsem hlφdat stav kdy je dek≤dovßn cel² soubor a p°ehrßvßnφ jeÜt∞ neskonΦilo. Takov² stav nastane v₧dy p°ed koncem p°ehrßvßnφ ogg souboru. Soubor bude v₧dy dek≤dovßn d°φv ne₧ bude cel² p°ehrßn. Kdy₧ je ale dek≤dovßn, nemß smysl u₧ volat funkci WriteDataToBuffer pro zapsßnφ nov²ch dat, co₧ souΦasnß t°φda ned∞lß. Dφky tomu dochßzelo k tomu, ₧e funkce WriteDataToBuffer zaΦala vracet v prom∞nnΘ dwDataDelta velikΘ hodnoty. Tj. v prom∞nnΘ kterß udßvß kolik z dwBytes po₧adovan²ch bajt∙ nebylo dek≤dovßno. P°i krokovßnφ jsem pak m∞l hodnotu dwDataDelta = dwBytes - 1. Vzdßlenost kurzor∙ zaΦala stßle r∙st a funkce WriteDataToBuffer se zaΦala volat p°i ka₧dΘm volßnφ funkce CheckIfNeedReFillBuffer se stßle v∞tÜφ vzdßlenostφ kurzor∙. Vzdßlenost kurzor∙ jak si jist∞ takΘ vzpomφnßte urΦuje kolik bajt∙ chceme zapsat do zvukovΘho bufferu, jak velkou pam∞t alokujeme p°i mezizßpisu do pam∞ti. Netrvalo dlouho a doÜlo k chyb∞ v programu proto₧e vzdßlenost kurzor∙ vzrostla natolik, ₧e u₧ nebylo mo₧nΘ alokovat tolik pam∞ti. Prost∞ doÜlo k chybnΘ alokaci a program spadl. D°φv jsem si toho nevÜiml, proto₧e ve svΘ p∙vodnφ t°φd∞ pou₧φvßm mφsto DataBuffer = new char[dwBytes] vlastnφ t°φdu pro prßci s °et∞zcov²mi, bajtov²mi poli, kterß to n∞jak vst°ebala. ╪eÜenφ nenφ a₧ tak slo₧itΘ, ale bude si vy₧adovat p°idßnφ ΦlenskΘ prom∞nnΘ m_dwWriteProgress do t°φdy COggFile. Tato prom∞nnß bude hlφdat pr∙b∞h zapisovßnφ, dek≤dovßnφ ogg souboru. V konstruktoru t°φdy nastavte tuto prom∞nnou na nula. Dßle ve funkci WriteDataToBuffer napiÜte m_dwWriteProgress += lSize; za dek≤dovacφ cyklus while s funkcφ ov_read. Poslednφ jsou ·pravy ve funkci CheckIfNeedReFillBuffer. Zm∞ny a p°φdavky jsou op∞t tuΦn∞:

    //**********************************************************************************************************
    //Fce pro kontrolu nutnosti zapisu novych dat do bufferu
    //**********************************************************************************************************

    HRESULT COggFile::CheckIfNeedReFillBuffer()
    {
       HRESULT hRet = S_FALSE;
       bool bRet = false;
       DWORD dwCurrPlayPos = 0;
       DWORD dwDataDelta = 0;
       DWORD dwCursorsDistance = 0;

       if (m_StreamedBuffer->IsSoundPlaying() || m_bStatus == OGGSTATUS_PAUSED)
       {
          //ziskani aktualni pozice hraciho kurzoru
          if(FAILED(hRet = m_StreamedBuffer->GetBuffer()->GetCurrentPosition(&dwCurrPlayPos,NULL)))
             return hRet;

          if ((m_dwPlayProgress + dwCurrPlayPos) >= m_dwPCMSize) //tady to se zpracovava pokud dojde k prehrani celeho souboru
          {
             hRet = m_StreamedBuffer->Stop(); //pokud dojde k prehrani celeho souboru, zastavime prehravani bufferu
             hRet = m_StreamedBuffer->Reset(); //a resetujeme buffer a prislusne promenne
             m_dwNextWriteOffset = 0; //zapis bude zacinat opet na nule
             m_dwPlayProgress = 0; //stejne tak pocet prehranzch bytu je nula
             m_dwLastPlayPos = 0; //posledni pozice hraciho kurzoru taktez
             dwCursorsDistance = 0; //vzdalenost hraciho a zapisovaciho kurzoru taktez nulova
             dwCurrPlayPos = 0;
             m_dwWriteProgress = 0;
             ov_raw_seek(&m_OggVorbisFile,0); //presun na zacatek v ogg souboru
             WriteDataToBuffer(0,m_dwBufferSize,NULL); //naplneni bufferu daty z ogg souboru od zacatku, kdyby byla pozdeji spustena fce play
             //tak at to tam je
             if (!m_iRepeatCount) //pokud je pocet opakovani 0
             {
                //nastavime stav zastaveno
                m_bStatus = OGGSTATUS_STOPPED;
                return S_FALSE; //vratime false jako indikaci ze nedoslo k obnoveni dat v bufferu
             }

             if (m_iRepeatCount != OGGPLAY_REPEATINFINITE) //pokud pocet opakovani neni nula a neni ani roven konstante pro
                m_iRepeatCount--; //nekonecno opakovani, snizime pocet opakovani

             hRet = m_StreamedBuffer->Play(0,DSBPLAY_LOOPING); //zacneme znovu prehravat buffer, pac se este neprehral tolikrat jak je nastaveno
             return hRet;
          }

          //pokud je posledni ulozena pozice hraciho kurzoru vetsi nez pozice kurzoru aktualni, znamena to ze
          //buffer dosahl konce a preskocil zpet na zacatek, podle toho musime pocitat vzdalenost kurzoru rozdilne
          if (m_dwLastPlayPos > dwCurrPlayPos)
             //hraci kurzor presel zpet na zacatek, musime vypocitat kolik bytu zbylo na konci bufferu neaktualizovanzch od posledni aktualizace
             //a pricist k nim pocet bytu co uz byly prehrany znova od zacatku a tak ziskame vzdalenost kurzoru
             //vzdalenost mezi hracim a zapisovacim kurzorem
             dwCursorsDistance = (m_dwBufferSize - m_dwNextWriteOffset) + dwCurrPlayPos;
          else
             //hraci kurzor je pred koncem, zapisovaci za nim, vzdalenost je rozdil jejich pozic
             //abychom nepocitali i bajt na kterem hraci kurzor zrovna je
             dwCursorsDistance = dwCurrPlayPos - m_dwNextWriteOffset;

          //ted se mrknem jestli vzdalenost kurzoru presahla pocet bajtu po kterych se ma buffer obnovovat
          if (dwCursorsDistance >= m_dwNotifySize) //a pokud ano, zapiseme do bufferu tolik bytu jako je vzdalenost kurzoru
          {
             if (m_dwWriteProgress < m_dwPCMSize)
             {
                bRet = WriteDataToBuffer(m_dwNextWriteOffset,dwCursorsDistance - 1,&dwDataDelta); //hraciho kurzoru, tedy mela by, muze se stat
                //vzdalenost mezi kurzory bude licha a v tom pripade nam fce ov_read ktera dekoduje ogg soubor

                m_dwNextWriteOffset += dwCursorsDistance - 1 - dwDataDelta; //vrati o nekolik bytu min, dekoduje totiz vzdy sudy pocet a ten pocet bajtu
                //musime snizit pozici dalsiho zapisovani, aby nevznikla jakasi dira, viz dwDataDelta
                if (m_dwNextWriteOffset >= m_dwBufferSize) //tady se hlida situace kdy je psaci kurzor skoro na konci a jeho aktualizaci o pocet zapanzch bajtu by doslo k prekroceni bufferu
                   m_dwNextWriteOffset -= m_dwBufferSize; //odecteme proto velikost bufferu a psaci kurzor se dostane na spravnou pozici od zacatku bufferu
             }


             if (m_dwLastPlayPos > dwCurrPlayPos)
             m_dwPlayProgress += m_dwBufferSize;

             m_dwLastPlayPos = dwCurrPlayPos; //aktualizace posledni pozice hraciho kurzoru
          }
       }
       return hRet;
    }

Tak doufßm ₧e to byla poslednφ zßva₧nß chyba v programu. N∞jakΘ menÜφ se najdou v₧dy. Doufßm ₧e se mi poda°ilo aspo≥ troÜku srozumiteln∞ vysv∞tlit jak to funguje a ₧e vßm to n∞co dalo. Pokud budete mφt n∞co na srdci (n∞co ohledn∞ kurzu, nejsem psychologickß poradna :)), m∙j email a icq pod hlavnφm menu nemajφ jen copyright funkci :) Klidn∞ piÜte a ptejte se. Rady na zlepÜenφ takΘ rßd p°ijmu. Kritiku..., no budu se s tφm muset n∞jak srovnat. Pokud budu v∞d∞t, rßd s p°φpadn²mi problΘmy pom∙₧u. Sna₧te se ale dßt co nejvφc informacφ. Na dotaz typu: nefunguje mi to! Co s tφm ud∞lßÜ? toho asi moc nevymyslφm :)

Obsah