fda9044e825879a6e7c6b8f2b4cd228a90541cb8
[swftools.git] / lib / h.263 / mkvideo.c
1 /* mkvideo.c
2    Create a video file.
3
4    Part of the swftools package.
5    
6    Copyright (c) 2003 Matthias Kramm <kramm@quiss.org> */
7
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <assert.h>
11 #include <math.h>
12 #include "../lib/rfxswf.h"
13 #include "png.h"
14 #include "h263tables.c"
15
16 typedef struct _VIDEOSTREAM
17 {
18     int width;
19     int height;
20     int frame;
21     RGBA*oldpic;
22     RGBA*current;
23 } VIDEOSTREAM;
24
25 void swf_SetVideoStreamDefine(TAG*tag, VIDEOSTREAM*stream, U16 frames, U16 width, U16 height)
26 {
27     width=width&~15; height=height&~15;
28     swf_SetU16(tag, frames);
29     swf_SetU16(tag, width);
30     swf_SetU16(tag, height);
31     swf_SetU8(tag, 1); /* smoothing on */
32     swf_SetU8(tag, 2); /* codec = h.263 sorenson spark */
33
34     memset(stream, 0, sizeof(VIDEOSTREAM));
35     stream->width = width&~15;
36     stream->height = height&~15;
37     stream->oldpic = (RGBA*)malloc(width*height*sizeof(RGBA));
38
39     memset(stream->oldpic, 0, width*height*sizeof(RGBA));
40 }
41
42 typedef struct _block_t
43 {
44     int y1[64];
45     int y2[64];
46     int y3[64];
47     int y4[64];
48     int u[64];
49     int v[64];
50 } block_t;
51
52 typedef struct _fblock_t
53 {
54     double y1[64];
55     double y2[64];
56     double y3[64];
57     double y4[64];
58     double u[64];
59     double v[64];
60 } fblock_t;
61
62 static void fzigzag(double*src) 
63 {
64     int table[64] = {
65         0, 1, 5, 6, 14, 15, 27, 28, 
66         2, 4, 7, 13, 16, 26, 29, 42, 
67         3, 8, 12, 17, 25, 30, 41, 43, 
68         9, 11, 18, 24, 31, 40, 44, 53, 
69         10, 19, 23, 32, 39, 45, 52, 54, 
70         20, 22, 33, 38, 46, 51, 55, 60, 
71         21, 34, 37, 47, 50, 56, 59, 61, 
72         35, 36, 48, 49, 57, 58, 62, 63};
73     double tmp[64];
74     int t;
75     for(t=0;t<64;t++) {
76         ((int*)&tmp[table[t]])[0] = ((int*)&src[t])[0];
77         ((int*)&tmp[table[t]])[1] = ((int*)&src[t])[1];
78     }
79     memcpy(src, tmp, sizeof(double)*64);
80 }
81
82 #define PI 3.14159265358979
83 #define SQRT2 1.414214
84 #define RSQRT2 (1.0/1.414214)
85
86 static double table[8][8] =
87 {
88 {0.707106781186548,0.707106781186548,0.707106781186548,0.707106781186548,0.707106781186548,0.707106781186548,0.707106781186548,0.707106781186548},
89 {0.980785280403230,0.831469612302545,0.555570233019602,0.195090322016128,-0.195090322016128,-0.555570233019602,-0.831469612302545,-0.980785280403230},
90 {0.923879532511287,0.382683432365090,-0.382683432365090,-0.923879532511287,-0.923879532511287,-0.382683432365090,0.382683432365090,0.923879532511287},
91 {0.831469612302545,-0.195090322016128,-0.980785280403230,-0.555570233019602,0.555570233019602,0.980785280403230,0.195090322016129,-0.831469612302545},
92 {0.707106781186548,-0.707106781186547,-0.707106781186548,0.707106781186547,0.707106781186548,-0.707106781186547,-0.707106781186547,0.707106781186547},
93 {0.555570233019602,-0.980785280403230,0.195090322016128,0.831469612302545,-0.831469612302545,-0.195090322016128,0.980785280403231,-0.555570233019602},
94 {0.382683432365090,-0.923879532511287,0.923879532511287,-0.382683432365090,-0.382683432365091,0.923879532511287,-0.923879532511286,0.382683432365090},
95 {0.195090322016128,-0.555570233019602,0.831469612302545,-0.980785280403231,0.980785280403230,-0.831469612302545,0.555570233019602,-0.195090322016129}
96 };
97
98 static void dct(double*src)
99 {
100     double tmp[64];
101     int x,y,u,v,t;
102
103     for(v=0;v<8;v++)
104     for(u=0;u<8;u++)
105     {
106         double c = 0;
107         for(x=0;x<8;x++)
108         {
109             c+=table[u][x]*src[v*8+x];
110         }
111         tmp[v*8+u] = c;
112     }
113     for(u=0;u<8;u++)
114     for(v=0;v<8;v++)
115     {
116         double c = 0;
117         for(y=0;y<8;y++)
118         {
119             c+=table[v][y]*tmp[y*8+u];
120         }
121         src[v*8+u] = c*0.25;
122     }
123 }
124
125 static void idct(double*src)
126 {
127     double tmp[64];
128     int x,y,u,v;
129     for(y=0;y<8;y++)
130     for(x=0;x<8;x++)
131     {
132         double c = 0;
133         for(u=0;u<8;u++)
134         {
135             c+=table[u][x]*src[y*8+u];
136         }
137         tmp[y*8+x] = c;
138     }
139     for(y=0;y<8;y++)
140     for(x=0;x<8;x++)
141     {
142         double c = 0;
143         for(v=0;v<8;v++)
144         {
145             c+=table[v][y]*tmp[v*8+x];
146         }
147         src[y*8+x] = c*0.25;
148     }
149 }
150
151 static void getregion(fblock_t* bb, RGBA*pic, int bx, int by, int width, int height)
152 {
153     RGBA*p1 = &pic[by*width*16+bx*16];
154     RGBA*p2 = p1;
155     int linex = width;
156     int y1=0, y2=0, y3=0, y4=0;
157     int u=0,v=0;
158     int x,y;
159     for(y=0;y<8;y++) {
160         for(x=0;x<8;x++) {
161             double r,g,b;
162             r = (p2[x*2].r + p2[x*2+1].r + p2[linex+x*2].r + p2[linex+x*2+1].r)/4.0;
163             g = (p2[x*2].g + p2[x*2+1].g + p2[linex+x*2].g + p2[linex+x*2+1].g)/4.0;
164             b = (p2[x*2].b + p2[x*2+1].b + p2[linex+x*2].b + p2[linex+x*2+1].b)/4.0;
165             bb->u[u++] = (r*-0.169 + g*-0.332 + b*0.500 + 128.0);
166             bb->v[v++] = (r*0.500 + g*-0.419 + b*-0.0813 + 128.0);
167
168             r = p1[x].r; g = p1[x].g; b = p1[x].b;
169             bb->y1[y1++] = (r*0.299 + g*0.587 + b*0.114);
170             r = p1[x+8].r; g = p1[x+8].g; b = p1[x+8].b;
171             bb->y2[y2++] = (r*0.299 + g*0.587 + b*0.114);
172             r = p1[linex*8+x].r; g = p1[linex*8+x].g; b = p1[linex*8+x].b;
173             bb->y3[y3++] = (r*0.299 + g*0.587 + b*0.114);
174             r = p1[linex*8+x+8].r; g = p1[linex*8+x+8].g; b = p1[linex*8+x+8].b;
175             bb->y4[y4++] = (r*0.299 + g*0.587 + b*0.114);
176         }
177         p1+=linex;
178         p2+=linex*2;
179     }
180 }
181
182 int compareregions(VIDEOSTREAM*s, int bx, int by)
183 {
184     int linex = s->width;
185     RGBA*p1 = &s->current[by*linex*16+bx*16];
186     RGBA*p2 = &s->oldpic[by*linex*16+bx*16];
187     int diff = 0;
188     int x,y;
189     for(y=0;y<16;y++) {
190         for(x=0;x<16;x++) {
191             RGBA*m = &p1[x];
192             RGBA*n = &p2[x];
193             int r = m->r - n->r;
194             int g = m->g - n->g;
195             int b = m->b - n->b;
196             diff += r*r+g*g+b*b;
197         }
198         p1+=linex;
199         p2+=linex;
200     }
201     return diff;
202 }
203
204 static int valtodc(int val)
205 {
206     assert(val>=0);
207
208     /* table 12/h.263 */
209
210     val+=4; //round
211     val/=8;
212     /* TODO: what to do for zero values? skip the block? */
213     if(val==0)
214         return 1;
215     if(val==128)
216         return 255;
217     if(val>254)
218         return 254;
219     return val;
220 }
221
222 static void codehuffman(TAG*tag, struct huffcode*table, int index)
223 {
224     /* TODO: !optimize! */
225     int i=0;
226     while(table[index].code[i]) {
227         if(table[index].code[i]=='0')
228             swf_SetBits(tag, 0, 1);
229         else
230             swf_SetBits(tag, 1, 1);
231         i++;
232     }
233 }
234
235 static void quantize8x8(double*src, int*dest, int has_dc, int quant)
236 {
237     int t,pos=0;
238     if(has_dc) {
239         dest[0] = valtodc((int)src[0]); /*DC*/
240         pos++;
241     }
242     for(t=pos;t<64;t++)
243     {
244         dest[t] = (int)src[t];
245         //val = (quant*(2*level+1)-1)+quant&1
246 /*      if(quant&1) {
247             dest[t] = (dest[t]/quant - 1)/2;
248         } else {
249             dest[t] = ((dest[t]+1)/quant - 1)/2;
250         }*/
251         //dest[t] = (dest[t]/quant-1)/2;
252         dest[t] = dest[t]/(quant*2);
253     }
254 }
255
256 static int hascoef(int*b, int has_dc)
257 {
258     int t;
259     int pos=0;
260     int range=2; /*TODO: should be a global parameter */
261     if(has_dc)
262         pos++;
263     for(t=pos;t<64;t++) {
264         if(b[t]<=-range || b[t]>=range)
265             return 1;
266     }
267     return 0;
268 }
269
270 static void encode8x8(TAG*tag, int*bb, int has_dc, int has_tcoef)
271 {
272     int t;
273     int pos=0;
274
275     if(has_dc) {
276         swf_SetBits(tag, bb[0], 8);
277         pos++;
278     }
279
280     if(has_tcoef) {
281         int last;
282         /* determine last non-null coefficient */
283         for(last=63;last>=pos;last--) {
284             /* TODO: we could leave out small coefficients
285                      after a certain point (32?) */
286             if(bb[last])
287                 break;
288         }
289         /* blocks without coefficients should not be included
290            in the cbpy/cbpc patterns: */
291         assert(bb[last]);
292
293         while(1) {
294             int run=0;
295             int level=0;
296             int islast=0;
297             int sign=0;
298             int t;
299             while(!bb[pos] && pos<last) {
300                 pos++;
301                 run++;
302             }
303             if(pos==last)
304                 islast=1;
305             level=bb[pos];
306             assert(level);
307             if(level<0) {
308                 level = -level;
309                 sign = 1;
310             }
311             for(t=0;t<RLE_ESCAPE;t++) {
312                 if(rle_params[t].run == run &&
313                    rle_params[t].level == level &&
314                    rle_params[t].last == islast) {
315                     codehuffman(tag, rle, t);
316                     swf_SetBits(tag, sign, 1);
317                     break;
318                 }
319             }
320             if(t==RLE_ESCAPE) {
321                 codehuffman(tag, rle, RLE_ESCAPE);
322                 level=bb[pos];
323                 /* table 14/h.263 */
324                 assert(level);
325                 if(level<-127) level = -127;
326                 if(level>127) level = 127;
327
328                 swf_SetBits(tag, islast, 1);
329                 swf_SetBits(tag, run, 6);
330                 swf_SetBits(tag, level, 8); //fixme
331             }
332
333             if(islast)
334                 break;
335             pos++;
336         }
337
338         //codehuffman(tag, rle, 58);
339         //swf_SetBits(tag, 1, 1); //sign
340     }
341 }
342
343 static void dodct(fblock_t*fb)
344 {
345     dct(fb->y1); dct(fb->y2); dct(fb->y3); dct(fb->y4); 
346     dct(fb->u);  dct(fb->v);  
347     fzigzag(fb->y1);
348     fzigzag(fb->y2);
349     fzigzag(fb->y3);
350     fzigzag(fb->y4);
351     fzigzag(fb->u);
352     fzigzag(fb->v); 
353 }
354
355 static void quantize(fblock_t*fb, block_t*b, int has_dc, int quant)
356 {
357     quantize8x8(fb->y1,b->y1,has_dc,quant); 
358     quantize8x8(fb->y2,b->y2,has_dc,quant); 
359     quantize8x8(fb->y3,b->y3,has_dc,quant); 
360     quantize8x8(fb->y4,b->y4,has_dc,quant); 
361     quantize8x8(fb->u,b->u,has_dc,quant);   
362     quantize8x8(fb->v,b->v,has_dc,quant);   
363 }
364
365 static void getblockpatterns(block_t*b, int*cbpybits,int*cbpcbits, int has_dc)
366 {
367     *cbpybits = 0;
368     *cbpcbits = 0;
369
370     *cbpybits|=hascoef(b->y1, has_dc)*8;
371     *cbpybits|=hascoef(b->y2, has_dc)*4;
372     *cbpybits|=hascoef(b->y3, has_dc)*2;
373     *cbpybits|=hascoef(b->y4, has_dc)*1;
374
375     *cbpcbits|=hascoef(b->u, has_dc)*2;
376     *cbpcbits|=hascoef(b->v, has_dc)*1;
377 }
378
379 static void setQuant(TAG*tag, int dquant)
380 {
381     int code = 0;
382     /* 00 01 10 11
383        -1 -2 +1 +2
384     */
385     if(dquant == -1) {
386         swf_SetBits(tag, 0x0, 2);
387     } else if(dquant == -2) {
388         swf_SetBits(tag, 0x1, 2);
389     } else if(dquant == +1) {
390         swf_SetBits(tag, 0x2, 2);
391     } else if(dquant == +2) {
392         swf_SetBits(tag, 0x3, 2);
393     } else {
394         assert(0*strlen("invalid dquant"));
395     }
396 }
397
398 static void change_quant(int quant, int*dquant)
399 {
400     /* TODO */
401     *dquant = 0;
402 }
403
404 void decode_blockI(VIDEOSTREAM*s, block_t*b, int bx, int by)
405 {
406     int x,y;
407     /* FIXME */
408     for(x=0;x<16;x++)
409     for(y=0;y<16;y++) {
410         s->oldpic[(y+by*16)*s->width+(x+bx*16)] = s->current[(y+by*16)*s->width+(x+bx*16)];
411     }
412 }
413
414 void encode_blockI(TAG*tag, VIDEOSTREAM*s, int bx, int by, int*quant)
415 {
416     fblock_t fb;
417     block_t b;
418     int dquant=0;
419     int cbpcbits = 0, cbpybits=0;
420
421     getregion(&fb, s->current, bx, by, s->width, s->height);
422     dodct(&fb);
423     
424     change_quant(*quant, &dquant);
425     *quant+=dquant;
426     quantize(&fb, &b, 1, *quant);
427
428     decode_blockI(s, &b, bx, by);
429
430     getblockpatterns(&b, &cbpybits, &cbpcbits, 1);
431
432     if(dquant) {
433         codehuffman(tag, mcbpc_intra, 4+cbpcbits);
434     } else {
435         codehuffman(tag, mcbpc_intra, 0+cbpcbits);
436     }
437
438     codehuffman(tag, cbpy, cbpybits);
439
440     if(dquant) {
441         setQuant(tag, dquant);
442     }
443
444     /* luminance */
445     encode8x8(tag, b.y1, 1, cbpybits&8);
446     encode8x8(tag, b.y2, 1, cbpybits&4);
447     encode8x8(tag, b.y3, 1, cbpybits&2);
448     encode8x8(tag, b.y4, 1, cbpybits&1);
449
450     /* chrominance */
451     encode8x8(tag, b.u, 1, cbpcbits&2);
452     encode8x8(tag, b.v, 1, cbpcbits&1);
453 }
454
455 void encode_blockP(TAG*tag, VIDEOSTREAM*s, int bx, int by, int*quant)
456 {
457     fblock_t fb;
458     block_t b;
459     int dquant=0;
460     int has_mvd=0;
461     int has_mvd24=0;
462     int has_dc=1;
463     int mode = 0;
464     int cbpcbits = 0, cbpybits=0;
465
466     {
467         int diff;
468         diff = compareregions(s, bx, by);
469         if(diff < 64/*TODO: should be a parameter*/) {
470             swf_SetBits(tag, 1,1); /* cod=1, block skipped */
471             return;
472         }
473     }
474
475     getregion(&fb, s->current, bx, by, s->width, s->height);
476     dodct(&fb);
477
478     change_quant(*quant, &dquant);
479     *quant += dquant;
480
481     quantize(&fb, &b, has_dc, *quant);
482
483     getblockpatterns(&b, &cbpybits, &cbpcbits, has_dc);
484
485     if(!dquant && has_mvd && !has_mvd24 && !has_dc) mode = 0;
486     else if(dquant && has_mvd && !has_mvd24 && !has_dc) mode = 1;
487     else if(!dquant && has_mvd && has_mvd24 && !has_dc) mode = 2;
488     else if(!dquant && !has_mvd && !has_mvd24 && has_dc) mode = 3;
489     else if(dquant && !has_mvd && !has_mvd24 && has_dc) mode = 4;
490     else exit(1);
491
492     swf_SetBits(tag,0,1); /* cod - 1 if we're not going to code this block*/
493         
494     codehuffman(tag, mcbpc_inter, mode*4+cbpcbits);
495     codehuffman(tag, cbpy, (mode==3 || mode==4)?cbpybits:cbpybits^15);
496
497     if(!bx&&!by) {
498         printf("cbpcbits: %d\n", cbpcbits);
499         printf("cbpybits: %d\n", cbpybits);
500     }
501
502     if(dquant) {
503         setQuant(tag, dquant);
504     }
505
506     if(has_mvd) {
507     }
508     if(has_mvd24) {
509     }
510
511     /* luminance */
512     encode8x8(tag, b.y1, has_dc, cbpybits&8);
513     encode8x8(tag, b.y2, has_dc, cbpybits&4);
514     encode8x8(tag, b.y3, has_dc, cbpybits&2);
515     encode8x8(tag, b.y4, has_dc, cbpybits&1);
516
517     /* chrominance */
518     encode8x8(tag, b.u, has_dc, cbpcbits&2);
519     encode8x8(tag, b.v, has_dc, cbpcbits&1);
520 }
521
522 #define TYPE_IFRAME 0
523 #define TYPE_PFRAME 1
524
525 static void writeHeader(TAG*tag, int width, int height, int frame, int quant, int type)
526 {
527     U32 i32;
528     swf_SetU16(tag, frame);
529     swf_SetBits(tag, 1, 17); /* picture start code*/
530     swf_SetBits(tag, 0, 5); /* version=0, version 1 would optimize rle behaviour*/
531     swf_SetBits(tag, frame, 8); /* time reference */
532
533     /* write dimensions, taking advantage of some predefined sizes
534        if the opportunity presents itself */
535     i32 = width<<16|height;
536     switch(i32)
537     {
538         case 352<<16|288: swf_SetBits(tag, 2, 3);break;
539         case 176<<16|144: swf_SetBits(tag, 3, 3);break;
540         case 128<<16|96: swf_SetBits(tag, 4, 3);break;
541         case 320<<16|240: swf_SetBits(tag, 5, 3);break;
542         case 160<<16|120: swf_SetBits(tag, 6, 3);break;
543         default:
544             if(width>255 || height>255) {
545                 swf_SetBits(tag, 1, 3);
546                 swf_SetBits(tag, width, 16);
547                 swf_SetBits(tag, height, 16);
548             } else {
549                 swf_SetBits(tag, 0, 3);
550                 swf_SetBits(tag, width, 8);
551                 swf_SetBits(tag, height, 8);
552             }
553     }
554
555     swf_SetBits(tag, type, 2); /* I-Frame or P-Frame */
556     swf_SetBits(tag, 0, 1); /* No deblock filter */
557     swf_SetBits(tag, quant, 5); /* quantizer (1-31), may be updated later on*/
558     swf_SetBits(tag, 0, 1); /* No extra info */
559 }
560
561 void swf_SetVideoStreamIFrame(TAG*tag, VIDEOSTREAM*s, RGBA*pic)
562 {
563     int bx, by, bbx, bby;
564     int quant = 7;
565
566     writeHeader(tag, s->width, s->height, s->frame, quant, TYPE_IFRAME);
567
568     bbx = (s->width+15)/16;
569     bby = (s->height+15)/16;
570
571     s->current = pic;
572
573     for(by=0;by<bby;by++)
574     {
575         for(bx=0;bx<bbx;bx++)
576         {
577             encode_blockI(tag, s, bx, by, &quant);
578         }
579     }
580     s->frame++;
581 }
582
583 void swf_SetVideoStreamPFrame(TAG*tag, VIDEOSTREAM*s, RGBA*pic)
584 {
585     int bx, by, bbx, bby;
586     int quant = 7;
587
588     writeHeader(tag, s->width, s->height, s->frame, quant, TYPE_PFRAME);
589
590     bbx = (s->width+15)/16;
591     bby = (s->height+15)/16;
592
593     s->current = pic;
594
595     for(by=0;by<bby;by++)
596     {
597         for(bx=0;bx<bbx;bx++)
598         {
599             encode_blockP(tag, s, bx, by, &quant);
600         }
601     }
602     s->frame++;
603 }
604
605 int main(int argn, char*argv[])
606 {
607     int fi;
608     int t;
609     SWF swf;
610     TAG * tag;
611     RGBA* pic, *pic2, rgb;
612     SWFPLACEOBJECT obj;
613     int width = 0;
614     int height = 0;
615     int frames = 100;
616     unsigned char*data;
617     char* fname = "/home/kramm/pics/peppers.png";
618     VIDEOSTREAM stream;
619     double d = 1.0;
620
621     memset(&stream, 0, sizeof(stream));
622
623     getPNG(fname, &width, &height, &data);
624     pic = (RGBA*)malloc(width*height*sizeof(RGBA));
625     pic2 = (RGBA*)malloc(width*height*sizeof(RGBA));
626     memcpy(pic, data, width*height*sizeof(RGBA));
627     free(data);
628
629     printf("Compressing %s, size %dx%d\n", fname, width, height);
630
631     memset(&swf,0,sizeof(SWF));
632     memset(&obj,0,sizeof(obj));
633
634     swf.fileVersion    = 6;
635     swf.frameRate      = 29*256;
636     swf.movieSize.xmax = 20*width;
637     swf.movieSize.ymax = 20*height;
638
639     swf.firstTag = swf_InsertTag(NULL,ST_SETBACKGROUNDCOLOR);
640     tag = swf.firstTag;
641     rgb.r = 0x00;rgb.g = 0x00;rgb.b = 0x00;
642     swf_SetRGB(tag,&rgb);
643
644     tag = swf_InsertTag(tag, ST_DEFINEVIDEOSTREAM);
645     swf_SetU16(tag, 33);
646     swf_SetVideoStreamDefine(tag, &stream, frames, width, height);
647     
648     for(t=0;t<frames;t++)
649     {
650         int x,y;
651         double xx,yy;
652         for(y=0,yy=0;y<height;y++,yy+=d)  {
653             RGBA*line = &pic[((int)yy)*width];
654             for(x=0,xx=0;x<width;x++,xx+=d) {
655                 pic2[y*width+x] = line[((int)xx)];
656             }
657         }
658         printf("frame:%d\n", t);fflush(stdout);
659
660         tag = swf_InsertTag(tag, ST_VIDEOFRAME);
661         swf_SetU16(tag, 33);
662         if(t==0)
663             swf_SetVideoStreamIFrame(tag, &stream, pic2);
664         else
665             swf_SetVideoStreamPFrame(tag, &stream, pic2);
666
667         tag = swf_InsertTag(tag, ST_PLACEOBJECT2);
668         swf_GetPlaceObject(0, &obj);
669         if(t==0) {
670             obj.depth = 1;
671             obj.id = 33;
672         } else {
673             obj.move = 1;
674             obj.depth = 1;
675             obj.ratio = t;
676         }
677         swf_SetPlaceObject(tag,&obj);
678
679         tag = swf_InsertTag(tag, ST_SHOWFRAME);
680         d-=0.005;
681     }
682    
683     tag = swf_InsertTag(tag, ST_END);
684
685     fi = open("video3.swf", O_WRONLY|O_CREAT|O_TRUNC, 0644);
686     if(swf_WriteSWF(fi,&swf)<0) {
687         fprintf(stderr,"WriteSWF() failed.\n");
688     }
689     close(fi);
690     swf_FreeTags(&swf);
691 }