summaryrefslogtreecommitdiffstats
path: root/DOCS/tech/tech-hun.txt
blob: 14477f3d4ee385b3ce4355206cd0a89915322bcd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
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 :)
   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!