d28794435ea60772e23757c7090b313e4f9c7d6c
[swftools.git] / lib / example / avi2swf.cc
1 /* avi2swf.cc
2    Convert avi movie files into swf.
3    As soon as there's an algorithm implemented for writing the
4    data directly to disk, this file should maybe go to ../src.
5
6    Part of the swftools package.
7    
8    Copyright (c) 2001 Matthias Kramm <kramm@quiss.org>
9
10    This file is distributed under the GPL, see file COPYING for details */
11
12 #include <stdio.h>
13 #include <fcntl.h>
14 extern "C" {
15 #include "../rfxswf.h"
16 #include "../args.h"
17 }
18 #include "avifile.h"
19 #include "aviplay.h"
20
21 /*
22    37   bytes per shape (rectangle)
23    8-12 bytes per placeobject
24    4    bytes per removeobject2
25    696+ bytes per definejpeg2 (576 jpegtables)
26    576  bytes per jpegtables
27    122+ bytes per definejpeg
28
29    blocks*140 = minimal bytes per frames
30    5000/140   = maximal blocks with framerate 5000
31
32    2    bytes per showframe
33 */
34
35 int cache_size=38; //in frames
36
37 char * filename = 0;
38 char * outputfilename = "output.swf";
39 unsigned int firstframe = 0;
40 unsigned int lastframe = 0x7fffffff;
41
42 int jpeg_quality = 20;
43
44 #ifndef ST_DEFINEBITSJPEG
45 #define ST_DEFINEBITSJPEG       6 
46 #endif
47
48 struct options_t options[] =
49 {
50  {"v","verbose"},
51  {"o","output"},
52  {"n","num"},
53  {"s","start"},
54  {"V","version"},
55  {0,0}
56 };
57
58 int args_callback_option(char*name,char*val)
59 {
60     if(!strcmp(name, "V")) {
61         printf("avi2swf - part of %s %s\n", PACKAGE, VERSION);
62         exit(0);
63     } 
64     else if(!strcmp(name, "o")) {
65         outputfilename = val;
66         return 1;
67     }
68     else if(!strcmp(name, "n")) {
69         lastframe = atoi(val);
70         return 1;
71     }
72     else if(!strcmp(name, "s")) {
73         firstframe = atoi(val);
74         return 1;
75     }
76 }
77 int args_callback_longoption(char*name,char*val)
78 {
79     return args_long2shortoption(options, name, val);
80 }
81 void args_callback_usage(char*name)
82 {    
83     printf("\nUsage: %s file.swf\n", name);
84     printf("\t-h , --help\t\t Print help and exit\n");
85     printf("\t-o , --output=filename\t Specify output filename\n"); 
86     printf("\t-n , --num=frames\t\t Number of frames to encode\n");
87     printf("\t-s , --start=frame\t\t First frame to encode\n");
88     printf("\t-V , --version\t\t Print program version and exit\n");
89     exit(0);
90 }
91 int args_callback_command(char*name,char*val)
92 {
93     if(filename) {
94         fprintf(stderr, "Only one file allowed. You supplied at least two. (%s and %s)\n",
95                  filename, name);
96     }
97     filename = name;
98     return 0;
99 }
100
101 /* id allocation/deallocation */
102 char idtab[65536];
103 unsigned int idtab_pos=1;
104 int get_free_id()
105 {
106     while(idtab[idtab_pos] || !idtab_pos)
107         idtab_pos++;
108     idtab[idtab_pos]=1;
109     return idtab_pos;
110 }
111 void free_id(int id)
112 {
113     idtab[id] = 0;
114 }
115
116 void makeshape(int file, int id, int gfxid, int width, int height)
117 {
118     TAG*tag;
119     RGBA rgb;
120     MATRIX m;
121     SHAPE*s;
122     SRECT r;
123     int lines = 0;
124     int ls,fs;
125     tag = swf_InsertTag(NULL, ST_DEFINESHAPE);
126     swf_ShapeNew(&s);
127     rgb.b = rgb.g = rgb.r = 0xff;
128     if(lines)
129         ls = swf_ShapeAddLineStyle(s,20,&rgb);  
130     swf_GetMatrix(NULL,&m);
131     m.sx = 20*65536;
132     m.sy = 20*65536;
133
134     fs = swf_ShapeAddBitmapFillStyle(s,&m,gfxid,0);
135     swf_SetU16(tag,id);   // ID   
136     r.xmin = 0;
137     r.ymin = 0;
138     r.xmax = width*20;
139     r.ymax = height*20;
140     swf_SetRect(tag,&r);
141
142     swf_SetShapeStyles(tag,s);
143     swf_ShapeCountBits(s,NULL,NULL);
144     swf_SetShapeBits(tag,s);
145
146     swf_ShapeSetAll(tag,s,0,0,lines?ls:0,fs,0);
147
148     swf_ShapeSetLine(tag,s,width*20,0);
149     swf_ShapeSetLine(tag,s,0,height*20);
150     swf_ShapeSetLine(tag,s,-width*20,0);
151     swf_ShapeSetLine(tag,s,0,-height*20);
152     swf_ShapeSetEnd(tag);
153     swf_WriteTag(file, tag);
154     swf_DeleteTag(tag);
155     swf_ShapeFree(s);
156 }
157
158 void setshape(int file,int id,int depth,int x,int y,CXFORM*cx)
159 {
160     TAG*tag;
161     MATRIX m;
162     m.sx = 0x10000; m.sy = 0x10000;
163     m.r0 = 0; m.r1 = 0;
164     m.tx = x*20; 
165     m.ty = y*20;
166     if(cx && !((cx->a0!=256)||(cx->r0!=256)||(cx->g0!=256)||(cx->b0!=256)
167                 ||(cx->a1|cx->r1|cx->g1|cx->b1))) cx = 0;
168     tag = swf_InsertTag(NULL,ST_PLACEOBJECT2);
169       swf_ObjectPlace(tag,id,depth,&m,cx,0);
170     swf_WriteTag(file, tag);
171     swf_DeleteTag(tag);
172 }
173
174
175 int xblocksize;
176 int yblocksize;
177 struct GfxBlock {
178 //    static int xblocksize;
179 //    static int yblocksize;
180     U8*data;
181     int len;
182 };
183
184 int width=0;
185 int height=0;
186
187 int xblocks;
188 int yblocks;
189
190 U8* blockbuffer = 0;
191    
192 class GfxBlockCache {
193
194     GfxBlock*list;
195     char*expire; //0=block's free
196     int*ids;
197     int size;
198     int pos;
199     int hits;
200     int misses;
201
202     public:
203
204     GfxBlockCache(int file) 
205     {
206         list=0;
207         size = xblocks*yblocks*cache_size;
208         printf("initializing cache (%d entries)\n", size);
209         list = new GfxBlock[size];
210         expire = new char[size];
211         ids = new int[size];
212         memset(expire,0,size);
213         memset(list,0,sizeof(GfxBlock)*size);
214         memset(ids,0,sizeof(int)*size);
215         pos = 0;
216         hits =0;
217         misses =0;
218     }
219     void insert(GfxBlock*block, int gfxid)
220     {
221         int oldpos = pos;
222         while(++pos!=oldpos)
223         {
224             if(pos==size) pos=0;
225             if(!expire[pos])
226                 break;
227         }
228         if(pos==oldpos) {
229             // cache full- don't insert item
230             return;
231         }
232         if(list[pos].data) {
233             free(list[pos].data);
234             list[pos].data = 0;
235             //TODO: free this in the SWF, also
236         }
237         list[pos].data=(U8*)malloc(block->len);
238         memcpy(list[pos].data,block->data,block->len);
239         list[pos].len = block->len;
240         expire[pos] = cache_size;
241         ids[pos] = gfxid;
242     }
243     int find(GfxBlock*block, CXFORM*cxform)
244     {
245         //TODO: do least square regression here to derive cxform
246         int s;
247         int bestsum=-1;
248         int bestid;
249         float best;
250         for(s=0;s<size;s++)
251         if(expire[s])
252         {
253             int t = (block->len);
254             U8*ptr1 = block->data;
255             U8*ptr2 = list[s].data;
256             int sum2 = 0;
257             // notice: we treat r,g,b as equal here.
258             do {
259                 int a = (*ptr1++)-(*ptr2++);
260                 sum2 += a*a;
261             } while(--t);
262             if(bestsum < 0 || bestsum > sum2) {
263                 bestid = s;
264                 bestsum = sum2;
265             }
266         }
267         if(bestsum<0) {
268             misses++;
269             return -1;
270         }
271         best = bestsum/block->len;
272
273         if(best > 96.0) {
274             misses++;
275             return -1;
276         } 
277         expire[bestid]= cache_size;
278         hits++;
279         cxform->a0 = 256;
280         cxform->r0 = 256;
281         cxform->g0 = 256;
282         cxform->b0 = 256;
283         cxform->a1 = 0;
284         cxform->r1 = 0;
285         cxform->g1 = 0;
286         cxform->b1 = 0;
287         return ids[bestid];
288     }
289     void newframe()
290     {
291         int t;
292         for(t=0;t<size;t++)
293             if(expire[t])
294                 expire[t]--;
295
296     }
297     ~GfxBlockCache()
298     {
299         int t;
300         printf("destroying cache...\n");
301         printf("hits:%d (%02d%%)\n", hits, hits*100/(hits+misses));
302         printf("misses:%d (%02d%%)\n", misses, misses*100/(hits+misses));
303         for(t=0;t<size;t++)
304             if(expire[t] && list[t].data)
305                 free(list[t].data);
306         free(list);
307         free(expire);
308         free(ids);
309     }
310 } * cache = 0;
311
312 class GfxBlockEncoder {
313     int sizex;
314     int sizey;
315     int posx;
316     int posy;
317     int basedepth;
318     int depth[3];
319     public:
320     void init(int depth, int posx,int posy, int sizex, int sizey) 
321     {
322         this->basedepth = depth;
323         this->posx = posx;
324         this->posy = posy;
325         this->sizex = sizex;
326         this->sizey = sizey;
327         this->depth[0] = this->depth[1] = this->depth[2] = -1;
328     }
329     void clear(int file)
330     {
331         /* clear everything in the block */
332         int t;
333         for(t=0;t<3;t++)
334         if(depth[t]>=0)
335         {
336             TAG*tag;
337             tag = swf_InsertTag(NULL, ST_REMOVEOBJECT2);
338             swf_SetU16(tag, basedepth+t); //depth
339             swf_WriteTag(file, tag);
340             swf_DeleteTag(tag);
341             depth[t] = -1;
342         }
343     }
344     void writeiframe(int file, GfxBlock*block)
345     {
346         clear(file);
347
348         int gfxid = get_free_id();
349         int shapeid = get_free_id();
350
351         //memset(data,0,sizex*sizey*3);
352         TAG*tag = swf_InsertTag(NULL, ST_DEFINEBITS);
353         JPEGBITS * jb = swf_SetJPEGBitsStart(tag,sizex,sizey,jpeg_quality);
354         tag->len = 0; //bad hack
355         swf_SetU16(tag, gfxid);
356         int y;
357         for(y=0;y<sizey;y++)
358             swf_SetJPEGBitsLine(jb,&block->data[y*sizex*3]);
359         swf_SetJPEGBitsFinish(jb);
360         swf_WriteTag(file, tag);
361         swf_DeleteTag(tag);
362
363         cache->insert(block, shapeid);
364
365         makeshape(file, shapeid, gfxid, sizex, sizey);
366         setshape(file, shapeid, basedepth+1, posx, posy, 0);
367         depth[1] = shapeid;
368     }
369     void writereference(int file, int shapeid, CXFORM*form)
370     {
371         if(depth[1]!=shapeid)
372         {
373             clear(file);
374             setshape(file, shapeid, basedepth+1, posx, posy, form);
375             depth[1] = shapeid;
376         }
377     }
378     void compress(int file, GfxBlock*block)
379     {   
380         CXFORM form;
381         int id = cache->find(block, &form);
382         if(id<0)
383             writeiframe(file, block);
384         else {
385             writereference(file, id, &form);
386         }
387     }
388 } *blocks = 0;
389
390 void initdisplay(int file)
391 {
392     if(blockbuffer)
393         free(blockbuffer);
394     if(blocks) {
395         int t;
396         for(t=0;t<xblocks;t++)
397             blocks[t].clear(file);
398         free(blocks);
399     }
400     if(cache)
401         delete cache;
402     xblocksize = (width/3)&~7;
403     yblocksize = (height/2)&~7;
404     xblocks = width/xblocksize;
405     yblocks = height/yblocksize;
406     printf("%dx%d blocks of size %dx%d\n", xblocks,yblocks, xblocksize, yblocksize);
407     printf("cutting lower %d lines, right %d columns\n", 
408             height-yblocks*yblocksize, width-xblocks*xblocksize);
409     blocks = new GfxBlockEncoder[xblocks*yblocks];
410     blockbuffer = new U8[xblocksize*yblocksize*4]; //should be 3
411     cache = new GfxBlockCache(file);
412     int t;
413     for(t=0;t<xblocks*yblocks;t++) {
414         blocks[t].init(t*64,
415                        (t%xblocks)*xblocksize,
416                        (t/xblocks)*yblocksize,
417                        xblocksize, yblocksize);
418     }
419
420     TAG*tag = swf_InsertTag(NULL, ST_JPEGTABLES);
421     JPEGBITS * jpeg = swf_SetJPEGBitsStart(tag, xblocksize, yblocksize, jpeg_quality);
422     swf_WriteTag(file, tag);
423     swf_DeleteTag(tag);
424     free(jpeg);
425 }
426
427 void destroydisplay(int file)
428 {
429     delete cache;
430     free(blocks);
431     free(blockbuffer);
432 }
433
434 SWF swf;
435 TAG*tag;
436
437 int main (int argc,char ** argv)
438
439   int file;
440   IAviReadFile* player;
441   IAviReadStream* astream;
442   IAviReadStream* vstream;
443   MainAVIHeader head;
444   SRECT r;
445
446   processargs(argc, argv);
447   lastframe += firstframe;
448   if(!filename)
449       exit(0);
450
451   memset(idtab, 0, sizeof(idtab));
452
453   player = CreateIAviReadFile(filename);    
454   player->GetFileHeader(&head);
455   printf("fps: %d\n", 1000000/head.dwMicroSecPerFrame);
456   printf("frames: %d\n", head.dwTotalFrames);
457   printf("streams: %d\n", head.dwStreams);
458   printf("streams: %d\n", player->StreamCount());
459   printf("width: %d\n", head.dwWidth);
460   printf("height: %d\n", head.dwHeight);
461   
462   astream = player->GetStream(0, AviStream::Audio);
463   vstream = player->GetStream(0, AviStream::Video);
464
465   vstream -> StartStreaming();
466
467   width = head.dwWidth;
468   height = head.dwHeight;
469   
470   file = open(outputfilename,O_WRONLY|O_CREAT|O_TRUNC, 0644);
471   
472   memset(&swf, 0, sizeof(swf));
473   swf.frameRate = (int)(1000000.0/head.dwMicroSecPerFrame*256);
474   swf.fileVersion = 4;
475   swf.fileSize = 0x0fffffff;
476   r.xmin = 0;
477   r.ymin = 0;
478   r.xmax = width*20;
479   r.ymax = height*20;
480   swf.movieSize = r;
481
482   swf_WriteHeader(file, &swf);
483
484   tag = swf_InsertTag(NULL, ST_SETBACKGROUNDCOLOR);
485   swf_SetU8(tag,0); //black
486   swf_SetU8(tag,0);
487   swf_SetU8(tag,0);
488   swf_WriteTag(file, tag);
489   swf_DeleteTag(tag);
490
491   int frame = 0;
492   initdisplay(file);
493
494   while(frame<firstframe) {
495     if(vstream->ReadFrame()<0)
496         break;
497     printf("\rskipping frame %d",frame);
498     frame++;
499   }
500   if(firstframe)
501   printf("\n");
502   
503   while(1) {
504     if(vstream->ReadFrame()<0) {
505         printf("\n");
506         break;
507     }
508     printf("\rconvert frame %d",frame);
509     fflush(stdout);
510     CImage*img = vstream->GetFrame();
511     img->ToRGB();
512     U8*data = img->data();
513     int bpp = img->bpp();
514     int x,y;
515     int xx,yy;
516     int fs,ls;
517     SHAPE*s;
518     MATRIX m;
519     SRECT r;
520     RGBA rgb;
521
522     /* some movies have changing dimensions */
523     if(img->width() != width ||
524        img->height() != height) {
525         printf("\n");
526         width = img->width();
527         height = img->height();
528         initdisplay(file);
529     }
530
531     for(yy=0;yy<yblocks;yy++)
532     for(xx=0;xx<xblocks;xx++) 
533     {
534         int x,y;
535         for(y=0;y<yblocksize;y++) {
536             U8*mydata = img->at(yy*yblocksize+y);
537             for(x=0;x<xblocksize;x++) {
538                 blockbuffer[(y*xblocksize+x)*3+2] = mydata[(xx*xblocksize+x)*3+0];
539                 blockbuffer[(y*xblocksize+x)*3+1] = mydata[(xx*xblocksize+x)*3+1];
540                 blockbuffer[(y*xblocksize+x)*3+0] = mydata[(xx*xblocksize+x)*3+2];
541             }
542         }
543         GfxBlock b;
544         b.data = blockbuffer;
545         b.len = xblocksize*yblocksize*3;
546         blocks[yy*xblocks+xx].compress(file, &b);
547     }
548
549     tag = swf_InsertTag(NULL, ST_SHOWFRAME);
550     swf_WriteTag(file, tag);
551     swf_DeleteTag(tag);
552
553     cache->newframe();
554
555     frame++;
556     if(frame == lastframe)
557         break;
558   }
559   printf("\n");
560   destroydisplay(file);
561   
562   tag = swf_InsertTag(NULL, ST_END);
563   swf_WriteTag(file, tag);
564   swf_DeleteTag(tag);
565
566   close(file);
567   
568   return 0;
569 }
570