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