Nos, akkor leírom, hogyan is működik ez az egész. A fő modulok: 1. streamer.c: ez az input layer, azaz ez olvassa a filet, VCD-t vagy stdin-t. amit tudnia kell: megfelelő sectoronkénti bufferelés, seek, skip funkciók, byte-onkénti ill. tetszőleges méretű blockonkénti olvasás. Egy stream (input device/file) leírására a stream_t struktúra szolgál. 2. demuxer.c: ez végzi az input szétszedését audio és video csatornákra, és a kiválasztott csatornák bufferelt package-enkénti olvasását. A demuxer.c inkább csak egy framework, ami közös minden input formátumra, és az egyes formátumokhoz (mpeg-es, mpeg-ps, avi, avi-ni, asf) külön parser van, ezek a demux_*.c file-okban vannak. A hozzá tartozó struktúra a demuxer_t. Összesen egy demuxer van. 2.a. demux_packet_t, azaz dp. ez egy darab chunk-ot (avi) vagy packet-et (asf, mpg) tartalmaz. memóriában ezek láncolt listában vannak, mivel különböző méretűek. 2.b. demuxer stream, azaz ds. struct: demux_stream_t minden egyes csatornához (a/v) tartozik egy ilyen. ez tartalmazza a stream-hez tartozó packeteket (lásd. 2.a.) egyelőre demuxer-enként 3 ilyen lehet: - hang (d_audio) - kép (d_video) - DVD felirat (d_dvdsub) 2.c. stream header. 2 féle van (egyelőre): sh_audio_t és sh_video_t ez tartalmaz minden, a dekódoláshoz szükséges paramétert, így az input és output buffereket, kiválasztott codecet, fps/framerate stb adatokat. Annyi van belőle, ahány stream van a file-ban tárolva. Lesz minimum egy a videohoz, ha van hang akkor ahhoz is, de ha több audio/video stream is van, akkor mindegyikhez lesz egy ilyen struct. Ezeket avi/asf esetén a header alapján tölti fel a header beolvasó, mpeg esetén pedig a demux_mpg.c fogja létrehozni, ha egy új streamet talál. Új stream esetén ====> Found audio/video stream: jelenik meg. A kiválasztott stream header és a hozzá tartozó demuxer stream kölcsönösen hivatkoznak egymásra (ds->sh és sh->ds) az egyszerűbb használat végett. (így a funkciótól függően elég vagy csak a ds vagy csak az sh átadása) Példa: van egy .asf file-unk, abban 6 db stream, ebből 1 audio és 5 video. A header beolvasásakor létre fog jönni 6 db sh struct, 1 audio és 5 video. Amikor elkezdi olvasni a packeteket, az első talált audio és video packethez tartozó streamet kivalasztja, es ezekre allitja be a d_audio és d_video sh pointereit. Így a későbbiekben már csak ezeket a streameket olvassa, a többit nem. Persze, ha a user másik streameket szeretne kiválasztani, akkor force-olhatja az -aid és -vid kapcsolókkal. Jó pelda erre a DVD, ahol nem mindig az angol szinkron hang az első megtalált stream, és így random minden vob más nyelven szólalhat meg :) Ilyenkor kell pl. az -aid 128 kapcsolót használni. hogy is műxik ez a beolvasósdi? - meghívódik a demuxer.c/demux_read_data(), megkapja melyik ds-ből (audio vagy video), mennyi byte-ot és hova (memóriacím) szeretnénk beolvasni. Ezt hívogatják gyakorlatilag a codec-ek. - ez megnézi, hogy az adott ds bufferében van-e valami, ha igen akkor onnan olvas, amennyit kell. Ha nincs/nincs elég, akkor meghívja a ds_fill_buffer()-t ami: - megnézi, hogy az adott ds-ben vannak-e bufferelve csomagok (dp-k) ha igen, akkor a legrégebbit átrakja a bufferbe és olvas tovább. Ha üres a láncolt lista, akkor meghívja a demux_fill_buffer()-t: - ez az input formátumnak megfelelő parser-t hívja meg, ami továbbol- vassa a file-t, és a talált csomagokat berakja a megfelelő bufferbe. Na, ha mondjuk audio csomagot szeretnénk, de csak egy rakat video csomag van, akkor jön előbb-utóbb a DEMUXER: Too many (%d in %d bytes) audio packets in the buffer... hibaüzenet. Eddig kb. tiszta ügy, ezt akarom majd átrakni külön lib-be. na nézzuk tovább: 3. mplayer.c - igen, ő a főnök :) az időzítes élég érdekesen van megoldva, főleg azért mert minden file- formátumnál másképp kell/célszerű, és néha többféle képpen is lehet. van egy a_frame és egy v_frame nevű float változó, ez tárolja az épp látható/hallható a/v pozícióját másodpercben. A lejátszó ciklus felépítése: while(not EOF) { fill audio buffer (read & decode audio) + increase a_frame read & decode a single video frame + increase v_frame sleep (wait until a_frame>=v_frame) display the frame apply A-V PTS correction to a_frame check for keys -> pause,seek,... } amikor lejátszik (hang/kép) akkor a lejátszott valami időtartamával növeli a megfelelő változót: - audionál ez a lejátszott byte-ok / sh_audio->o_bps megj.: i_bps = tömörített byte-ok széma egy másodpercnyi hanghoz o_bps = tömörítetlen byte-ok száma egy másodpercnyi hanghoz (ez utóbbi == bps*samplerate*channels) - videonál ez általában az sh_video->frametime. Ez általában == 1.0/fps, persze meg kell jegyeznem, hogy videonál nem igazán számít az fps, asf-nél pl. nincs is olyan, ahelyett duration van és frame-enként változhat. mpeg2-nél pedig repeat_count van, ami 1-2.5 időtartamban elnyújtja a frame-et... avi-nál van talán egyedül fix fps, meg mpeg1-nél. Na most ez addig nagyon szépen működik, amíg a hang és kép tökéletes szinkronban van, mivel így végülis a hang szól, az adja az időzítést, és amikor eltelt egy frame-nyi idő, akkor kirakja a következő frame-et. De mi van, ha valamiért az input file-ban csúszik a kettő? Akkor jön be a PTS correction. Az input demuxer-ek olvassák a csomagokkal együtt a hozzájuk tartozó PTS-t (presentation timestamp) is, ami alapján észrevehető, ha el van csúszva a kettő. Ilyenkor egy megadott maximális határon (lásd -mc opció) belül képes az mplayer korrigalni az a_frame értékét. A korrekciók összege van a c_total-ban. Persze ez még nem minden szinkron ügyben, van még némi gáz. Pl. az, hogy a hangkártya elég rendesen késleltet, ezt az mplayernek korrigálnia kell! Az összes audio késleltetés másodpercben ezek összege: - az utolsó timestamp (PTS) óta beolvasott byte-ok: t1 = d_audio->pts_bytes/sh_audio->i_bps - Win32/ACM esetén az audio input bufferben tárolt byte-ok: t2 = a_in_buffer_len/sh_audio->i_bps - az audio out bufferben tárolt tömörítetlen byte-ok: t3 = a_buffer_len/sh_audio->o_bps - a hangkártya bufferében (vagy DMA bufferben) tárolt, még nem lejátszott byte-ok: t4 = get_audio_delay()/sh_audio->o_bps Ezekből kiszámolható egészen pontosan, hogy az épp hallható hanghoz milyen PTS tartozik, majd ezt összevetve a video-hoz tartozo PTS-el meg is kapjuk az A-V eltérését! Avi-nál sem egyszerű az élet. Ott a 'hivatalos' időzítési mód a BPS-alapú, azaz a headerben le van tárolva, hány tömörített audio byte tartozik egy másodpercnyi (fps darab) képhez. ez persze nem mindig működik... miért is működne :) Ezért én megcsináltam, hogy az mpeg-nél használatos sectoronkénti PTS értéket emulálom avi-ra is, azaz az AVI parser minden beolvasott chunk-nál számol egy kamu PTS-t a frame-ek típusa alapján, és ez alapjan idozitek. És van amikor ez működik jobban. Persze itt még bejátszik az is, hogy AVI-nál általában előre letárolnak egy nagyobb adag hangot, és csak utána kezdődik a kép. Ezt persze bele kell számolni a késleltetésbe, ez az Initial PTS delay. Ilyen persze 2 is van, az egyik a headerben le is van írva, és nem nagyon használják. :) A másik sehol nincs leírva, de használják, ezt csak mérni lehet... 3.a. audio playback: pár szó az audio lejátszásról: az egészben nem maga a lejátszás a nehéz, hanem: 1. hogy tudjuk, mikor lehet írni a bufferbe, blocking nélkül 2. hogy tudjuk, mennyit játszott már le abból, amit a bufferbe írtunk Az 1. az audio dekódoláshoz kell, valamint hogy a buffert mindig teli állapotban tudjuk tartani (így sose fog megakadni a hang). A 2. pedig a korrekt időzítéshez szükséges, ugyanis némely hangkártya akár 3-7 másodpercet is késleltet, ami azért nem elhanyagolható! Ezek megvalósítására az OSS többféle lehetőséget is kínál: - ioctl(SNDCTL_DSP_GETODELAY): megmondja, hány lejátszatlan byte várakozik a hangkártya bufferében -> időzítéshez kiváló, de nem minden driver támogatja :( - ioctl(SNDCTL_DSP_GETOSPACE): megmondja, mennyit írhatunk a kártya bufferébe blocking nélkül. Ha a driver nem tudja a GETODELAY-t, akkor ezt hasznalhatjuk arra is, hogy megtudjuk a késleltetést. - select(): meg kéne mondja, hogy írhatunk-e a kártya bufferébe blocking nélkül. Azt, hogy mennyit írhatunk, nem mondja meg :( valamint sok driverrel egyáltalán nem, vagy rosszul működik :(( csak akkor használom, ha egyik fenti ioctl() sem működik. 4. codecek. ezek különböző lib-ek szanaszét mindenfelől. mint pl. libac3, libmpeg2, xa/*, alaw.c, opendivx/*, loader, mp3lib. az mplayer.c hívogatja őket, amikor egy-egy darab hangot vagy frame-et kell lejátszani (lásd 3. pont elején). ezek pedig hívják a megfelelő demuxert, hogy megkapják a tömörített adatokat (lásd 2. pont). paraméterként a megfelelő stream headert (sh_audio/sh_video) kell átadni, ez elvileg tartalmaz minden infót, ami szükséges a dekódoláshoz (többek között a demuxert is: sh->ds). A codecek szeparálasa folyamatban van, az audio már el van különítve (lásd. dec_audio.c), a videon még dolgozunk. Cél, hogy ne az mplayer.c kelljen tudja, milyen codecek vannak és hogy kell őket használni, hanem egy közös init/decode audio/video functiont kelljen csak meghívnia. 5. libvo: ez végzi a kép kirakását. Az img_format.h-ban definiálva vannak konstansok a különböző pixel- formátumokhoz, ezeket kötelező használni. 1-1 vo drivernek a következőket kell kötelezően implementálnia: query_format() - lekérdezi, hogy egy adott pixelformat támogatott-e. return value: flags: 0x1 - supported (by hardware or with conversion) 0x2 - supported (by hardware, without conversion) 0x4 - sub/osd supported (has draw_alpha) FONTOS: minden vo drivernek kötelező támogatnia az YV12 formátumot, és egyiket (vagy mindkettőt) a BGR15 és BGR24 közül, ha kell, konvertálással. Ha ezeket nem támogatja, akkor nem fog minden codec-kel működni! Ennek az az oka, hogy az mpeg codecek csak YV12-t tudnak előállítani, a régebbi Win32 DLL codecek pedig csak 15 és 24bpp-t tudnak. Van egy gyors MMX-es 15->16bpp konvertáló, így az nem okoz különösebb sebességcsökkenést! A BPP táblázat, ha a driver nem tud bpp-t váltani: jelenlegi bpp: ezeket kell elfogadni: 15 15 16 15,16 24 24 24,32 24,32 Ha tud bpp-t váltani (pl. DGA 2, fbdev, svgalib) akkor, ha lehet, be kell váltani a kért bpp-re. Ha azt a hardver nem támogatja, akkor a legközelebbi módra (15 esetén 16-ra, 24 esetén 32-re) kell váltani és konvertálni! init() - ez hívódik meg a legelső frame kirakása előtt - bufferek foglalása stb a célja. van egy flags paraméter is (régen fullscreen volt a neve): 0x01 - fullscreen (-fs) 0x02 - vidmode switch (-vm) 0x04 - scaling enabled (-zoom) 0x08 - flip image (upside-down) draw_slice(): ez planar YV12 képet rak ki (3 db plane, egy teljes méretű, ami a fényerőt (Y) tartalmazza, és 2 negyedakkora, ami a szín (U,V) infót). ezt használják az mpeg codecek (libmpeg2, opendivx). ez már tud olyat, hogy nem az egész kép kirakása, hanem csak kis részletek update-elése: ilyenkor a sarkának és a darabka méretének megadásával lehet használni. draw_frame(): ez a régebbi interface, ez csak komplett frame-et rak ki, és csak packed formátumot (YUY2 stb, RGB/BGR) tud. ezt használják a win32 codecek (divx, indeo stb). draw_alpha(): ez rakja ki a subtitle-t és az OSD-t. használata kicsit cseles, mivel ez nem a libvo API része, hanem egy callback jellegű cucc. a flip_page() kell meghívja a vo_draw_text()-et úgy, hogy paraméterként átadja a képernyő méreteit és a pixel- formátumnak megfelelő draw_alpha() implementációt (function pointer). Ezután a vo_draw_text() végigmegy a kirajzolandó karaktereken, és egyenként meghívja minden karakterre a draw_alpha()-t. Segítség képpen az osd.c-ben meg van írva a draw_alpha mindenféle pixelformátumhoz, ha lehet ezt használd! flip_page(): ez meghívódik minden frame után, ennek kell ténylegesen meg- jeleníteni a buffert. double buffering esetén ez lesz a 'swapbuffers'. 6. libao2: ez vezérli a hang lejátszást A libvo-hoz (lásd 5.) hasonlóan itt is különböző driverek vannak, amik egy közös API-t (interface) valósítanak meg: static int control(int cmd,int arg); Ez egy általános célú függvény, a driverfüggő és egyéb speciális paraméterek olvasására/beállítására. Egyelőre nem nagyon használt. static int init(int rate,int channels,int format,int flags); Driver initje, ilyenkor kell megnyitni a device-t, beállítani samplerate, channels, sample format paramétereket. Sample format: általában AFMT_S16_LE vagy AFMT_U8, további definíciókért lásd. dec_audio.c ill. linux/soundcard.h file-okat! static void uninit(); Találd ki! Na jó, segítek: lezárja a device-t, kilépéskor (még nem) hívódik meg. static void reset(); Reseteli a device-t. Egész pontosan a bufferek törlésére szolgál, tehát hogy a reset() után már ne szóljon tovább az, amit előtte kapott. (pause ill. seek esetén hívódik meg) static int get_space(); Vissza kell adja, hogy hány byte írható az audio bufferbe anélkül, hogy blockolna (várakoztatná a hívó processt). Amennyiben a buffer (majdnem) tele van, 0-t kell visszaadni! Ha sosem ad vissza 0-t, akkor nem fog működni az MPlayer! static int play(void* data,int len,int flags); Lejátszik egy adag hangot, amit a data című memóriaterületen kap és len a mérete. a flags még nem használt. Az adatokat át kell másolnia, mert a hívás után felülíródhatnak! Nem kell feltétlen minden byte-ot felhasználni, hanem azt kell visszaadnia, mennyit használt fel (másolt a bufferbe). static int get_delay(); Vissza kell adja, hogy hány byte várakozik az audio bufferben. lehetőleg minél pontosabban, mert ettől függ az egész időzítés! Legrosszabb esetben adja vissza a buffer méretét! !!! Mivel a kép a hanghoz (hangkártyához) van szinkronizálva, így nagyon fontos, !!! hogy a get_space ill. get_delay függvények korrektül legyenek megírva!