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