mpv/DOCS/tech/tech-hun.txt

295 lines
14 KiB
Plaintext

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: <id> 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 :)
Fő feladata a különböző modulok összekapcsolása, illetve az A-V
szinkron biztosítása.
Az adott stream aktuális pozíciója a megfelelo stream header
(sh_audio / sh_video) timer field-ben van.
(Régen ez volt az a_frame és egy v_frame nevű float változó)
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 vagy chunk tartozik egy másodpercnyi (fps darab) képhez.
Az AVI stream headerben van 2 fontos mezo, a dwSampleSize, es
a dwRate/dwScale aránypár:
- Ha a dwSampleSize 0, akkor VBR stream, tehat nem konstans a
bitrate. Ilyenkor 1 chunk tarol 1 sample-t, es a masodpercenkenti
chunkok szamat adja a dwRate/dwScale.
- Ha a dwSampleSize>0, akkor constant bitrate van, es az ido igy
szamolhato: time = (bytepos/dwSampleSize) / (dwRate/dwScale)
(tehat a sample sorszamat elosztjuk a samplerate-el)
Ilyenkor stream-kent kezelheto az audio, ami tetszolegesen
chunk-okra van darabolva, de lehet akar 1 db chunk is az egesz.
A másik lehetőség csak az interleaved fileoknál használható: a
chunk-ok sorrendjéből számolható egy timestamp (PTS) érték.
A video chunkok PTS-e egyszerű: chunk száma * fps
Az audio pedig az előtte levő video chunk-éval azonos.
Ilyenkor viszont szamolni kell az ugynev. "audio preload"-al is,
azaz van egy fix kesleltetes az audio es video stream-ek kozott.
Ez altalaban 0.5-1.0 sec, de van amikor egeszen mas.
A pontos erteket regen mertuk, most a demux_avi.c kezeli le:
az elso video utani audio chunknal kiszamolja az A-V elterest,
es ezt veszi az audio preload mertekenek.
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 nem kozvetlenul hivja oket, hanem a dec_audio.c es a
dec_video.c fileokon keresztul, igy az mplayer.c-nek nem kell semmit
sem tudnia a codecrol.
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!