fixed sound.
[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   double samplesperframe;
446   int samplerate;
447   int samplefix;
448
449   processargs(argc, argv);
450   lastframe += firstframe;
451   if(!filename)
452       exit(0);
453
454   memset(idtab, 0, sizeof(idtab));
455
456   player = CreateIAviReadFile(filename);    
457   player->GetFileHeader(&head);
458   printf("fps: %d\n", 1000000/head.dwMicroSecPerFrame);
459   printf("frames: %d\n", head.dwTotalFrames);
460   printf("streams: %d\n", head.dwStreams);
461   printf("streams: %d\n", player->StreamCount());
462   printf("width: %d\n", head.dwWidth);
463   printf("height: %d\n", head.dwHeight);
464   
465   astream = player->GetStream(0, AviStream::Audio);
466   vstream = player->GetStream(0, AviStream::Video);
467
468   vstream -> StartStreaming();
469   astream -> StartStreaming();
470
471   width = head.dwWidth;
472   height = head.dwHeight;
473
474   printf("sound: %u samples (%f seconds)\n", astream->GetEndPos(),
475           astream->GetEndTime());
476   samplesperframe = astream->GetEndPos()/astream->GetEndTime()*head.dwMicroSecPerFrame/1000000;
477   printf("%f samples/frame\n", samplesperframe);
478   samplerate = (int)(astream->GetEndPos()/astream->GetEndTime());
479   printf("%d samplerate\n", samplerate);
480   samplefix = 44100/samplerate;
481
482   if(!samplefix) {
483       printf("samplerate too high!\n");
484       return 0;
485   }
486   printf("%d mp3 samples per movie sample\n", samplefix);
487
488   file = open(outputfilename,O_WRONLY|O_CREAT|O_TRUNC, 0644);
489   
490   memset(&swf, 0, sizeof(swf));
491   swf.frameRate = (int)(1000000.0/head.dwMicroSecPerFrame*256);
492   swf.fileVersion = 4;
493   swf.fileSize = 476549;//0x0fffffff;
494   swf.frameCount = lastframe - firstframe;
495   r.xmin = 0;
496   r.ymin = 0;
497   r.xmax = width*20;
498   r.ymax = height*20;
499   swf.movieSize = r;
500
501   swf_WriteHeader(file, &swf);
502
503   tag = swf_InsertTag(NULL, ST_SETBACKGROUNDCOLOR);
504   swf_SetU8(tag,0); //black
505   swf_SetU8(tag,0);
506   swf_SetU8(tag,0);
507   swf_WriteTag(file, tag);
508   swf_DeleteTag(tag);
509
510   tag = swf_InsertTag(NULL, ST_SOUNDSTREAMHEAD2);
511   swf_SetSoundStreamHead(tag, 1152);
512   swf_WriteTag(file, tag);
513   swf_DeleteTag(tag);
514
515   int frame = 0;
516   initdisplay(file);
517
518   int mp3_block_size = 1152;
519
520   int bufsize = mp3_block_size;
521   if(mp3_block_size < (int)(samplesperframe+1))
522           bufsize = (int)(samplesperframe + 1);
523   unsigned char*buffer = (unsigned char*)malloc(bufsize);
524   short*block = (short*)malloc(bufsize*2*samplefix);
525
526   unsigned samples_read, bytes_read;
527
528   double movie_sound_pos = 0;
529   int mp3_sound_pos = 0;
530
531   WAVEFORMATEX wave;
532   astream->GetAudioFormatInfo(&wave,0);
533
534   printf("nChannels:%d\n", wave.nChannels);
535   printf("nSamplesPerSec:%d\n", wave.nChannels);
536   printf("nAvgBytesPerSec:%d\n", wave.nAvgBytesPerSec);
537   printf("nBlockAlign:%d\n", wave.nBlockAlign);
538   printf("wBitsPerSample:%d\n", wave.wBitsPerSample);
539   printf("cbSize:%d\n", wave.cbSize);
540
541   while(1) {
542     if(vstream->ReadFrame()<0) {
543         printf("\n");
544         break;
545     }
546
547     if(frame < firstframe)
548     {
549         if(astream->ReadFrames(buffer, bufsize,
550                     (int)samplesperframe,
551                 samples_read, bytes_read)<0) {
552             printf("\n");
553             break;
554         };
555         printf("\rskipping frame %d",frame);
556         fflush(stdout);
557         frame++;
558         if(frame == firstframe)
559             printf("\n");
560         continue;
561     }
562
563     printf("\rconvert frame %d",frame);
564     fflush(stdout);
565
566     // audio
567     movie_sound_pos += samplesperframe;
568
569     int first=1;
570     while(mp3_sound_pos<movie_sound_pos) {
571         if(astream->ReadFrames(buffer, bufsize,
572                     mp3_block_size/samplefix,
573                 samples_read, bytes_read)<0) {
574             printf("couldn't read %d samples\n", mp3_block_size);
575             break;
576         };
577         int t=0;
578         int s;
579         int c=0;
580         for(s=0;s<mp3_block_size;s++) {
581             block[s] = ((int)buffer[t]-128)*256;
582             c++;
583             if(c==samplefix) {
584                 t++;
585                 c=0;
586             }
587         }
588         if(first) { //first run
589             tag = swf_InsertTag(NULL, ST_SOUNDSTREAMBLOCK);
590               swf_SetSoundStreamBlock(tag, block, mp3_block_size,1);
591         } else {
592               swf_SetSoundStreamBlock(tag, block, mp3_block_size,0);
593         }
594         
595         mp3_sound_pos += mp3_block_size/samplefix;
596
597         if(mp3_sound_pos>=movie_sound_pos) { // last run
598             swf_WriteTag(file, tag);
599             swf_DeleteTag(tag);
600         }
601         first = 0;
602     }
603  
604     // video
605
606     CImage*img = vstream->GetFrame();
607     img->ToRGB();
608     U8*data = img->data();
609     int bpp = img->bpp();
610     int x,y;
611     int xx,yy;
612     int fs,ls;
613     SHAPE*s;
614     MATRIX m;
615     SRECT r;
616     RGBA rgb;
617
618     /* some movies have changing dimensions */
619     if(img->width() != width ||
620        img->height() != height) {
621         printf("\n");
622         width = img->width();
623         height = img->height();
624         initdisplay(file);
625     }
626
627     for(yy=0;yy<yblocks;yy++)
628     for(xx=0;xx<xblocks;xx++) 
629     {
630         int x,y;
631         for(y=0;y<yblocksize;y++) {
632             U8*mydata = img->at(yy*yblocksize+y);
633             for(x=0;x<xblocksize;x++) {
634                 blockbuffer[(y*xblocksize+x)*3+2] = mydata[(xx*xblocksize+x)*3+0];
635                 blockbuffer[(y*xblocksize+x)*3+1] = mydata[(xx*xblocksize+x)*3+1];
636                 blockbuffer[(y*xblocksize+x)*3+0] = mydata[(xx*xblocksize+x)*3+2];
637             }
638         }
639         GfxBlock b;
640         b.data = blockbuffer;
641         b.len = xblocksize*yblocksize*3;
642         blocks[yy*xblocks+xx].compress(file, &b);
643     }
644
645     tag = swf_InsertTag(NULL, ST_SHOWFRAME);
646     swf_WriteTag(file, tag);
647     swf_DeleteTag(tag);
648
649     cache->newframe();
650
651     frame++;
652     if(frame == lastframe)
653         break;
654   }
655   printf("\n");
656   destroydisplay(file);
657
658   printf("mp3 samples read:%d\n", mp3_sound_pos);
659   printf("mp3 samples read:%d\n", mp3_sound_pos);
660   
661   tag = swf_InsertTag(NULL, ST_END);
662   swf_WriteTag(file, tag);
663   swf_DeleteTag(tag);
664
665   close(file);
666   
667   return 0;
668 }
669