Add version info to celtenc/celtdec output as suggested on the
[opus.git] / tools / celtenc.c
1 /* Copyright (C) 2002-2008 Jean-Marc Valin 
2    Copyright (C) 2008-2009 Gregory Maxwell
3    File: celtenc.c
4
5    Redistribution and use in source and binary forms, with or without
6    modification, are permitted provided that the following conditions
7    are met:
8    
9    - Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11    
12    - Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15    
16    - Neither the name of the Xiph.org Foundation nor the names of its
17    contributors may be used to endorse or promote products derived from
18    this software without specific prior written permission.
19    
20    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23    A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
24    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #ifdef HAVE_CONFIG_H
34 # include "config.h"
35 #endif
36
37 #include <stdio.h>
38 #if !defined WIN32 && !defined _WIN32
39 #include <unistd.h>
40 #endif
41
42 #ifdef HAVE_GETOPT_H
43 #include <getopt.h>
44 #endif
45
46 #ifndef HAVE_GETOPT_LONG
47 #include "getopt_win.h"
48 #endif
49
50 #include <stdlib.h>
51 #include <string.h>
52 #include <time.h>
53
54 #include "celt.h"
55 #include "celt_header.h"
56 #include <ogg/ogg.h>
57 #include "wav_io.h"
58
59 #if defined WIN32 || defined _WIN32
60 /* We need the following two to set stdout to binary */
61 #include <io.h>
62 #include <fcntl.h>
63 #endif
64
65 #include "skeleton.h"
66
67
68 void comment_init(char **comments, int* length, char *vendor_string);
69 void comment_add(char **comments, int* length, char *tag, char *val);
70
71
72 /*Write an Ogg page to a file pointer*/
73 int oe_write_page(ogg_page *page, FILE *fp)
74 {
75    int written;
76    written = fwrite(page->header,1,page->header_len, fp);
77    written += fwrite(page->body,1,page->body_len, fp);
78    
79    return written;
80 }
81
82 #define MAX_FRAME_SIZE 2000
83 #define MAX_FRAME_BYTES 2000
84
85 /* Convert input audio bits, endians and channels */
86 static int read_samples(FILE *fin,int frame_size, int bits, int channels, int lsb, short * input, char *buff, celt_int32_t *size)
87 {   
88    unsigned char in[MAX_FRAME_BYTES*2];
89    int i;
90    short *s;
91    int nb_read;
92
93    if (size && *size<=0)
94    {
95       return 0;
96    }
97    /*Read input audio*/
98    if (size)
99       *size -= bits/8*channels*frame_size;
100    if (buff)
101    {
102       for (i=0;i<12;i++)
103          in[i]=buff[i];
104       nb_read = fread(in+12,1,bits/8*channels*frame_size-12, fin) + 12;
105       if (size)
106          *size += 12;
107    } else {
108       nb_read = fread(in,1,bits/8*channels* frame_size, fin);
109    }
110    nb_read /= bits/8*channels;
111
112    /*fprintf (stderr, "%d\n", nb_read);*/
113    if (nb_read==0)
114       return 0;
115
116    s=(short*)in;
117    if(bits==8)
118    {
119       /* Convert 8->16 bits */
120       for(i=frame_size*channels-1;i>=0;i--)
121       {
122          s[i]=(in[i]<<8)^0x8000;
123       }
124    } else
125    {
126       /* convert to our endian format */
127       for(i=0;i<frame_size*channels;i++)
128       {
129          if(lsb) 
130             s[i]=le_short(s[i]); 
131          else
132             s[i]=be_short(s[i]);
133       }
134    }
135
136    /* FIXME: This is probably redundent now */
137    /* copy to float input buffer */
138    for (i=0;i<frame_size*channels;i++)
139    {
140       input[i]=(short)s[i];
141    }
142
143    for (i=nb_read*channels;i<frame_size*channels;i++)
144    {
145       input[i]=0;
146    }
147
148
149    return nb_read;
150 }
151
152 void add_fishead_packet (ogg_stream_state *os) {
153
154    fishead_packet fp;
155
156    memset(&fp, 0, sizeof(fp));
157    fp.ptime_n = 0;
158    fp.ptime_d = 1000;
159    fp.btime_n = 0;
160    fp.btime_d = 1000;
161
162    add_fishead_to_stream(os, &fp);
163 }
164
165 /*
166  * Adds the fishead packets in the skeleton output stream along with the e_o_s packet
167  */
168 void add_fisbone_packet (ogg_stream_state *os, celt_int32_t serialno, CELTHeader *header) {
169
170    fisbone_packet fp;
171
172    memset(&fp, 0, sizeof(fp));
173    fp.serial_no = serialno;
174    fp.nr_header_packet = 2 + header->extra_headers;
175    fp.granule_rate_n = header->sample_rate;
176    fp.granule_rate_d = 1;
177    fp.start_granule = 0;
178    fp.preroll = 3;
179    fp.granule_shift = 0;
180
181    add_message_header_field(&fp, "Content-Type", "audio/x-celt");
182
183    add_fisbone_to_stream(os, &fp);
184 }
185
186 void version(void)
187 {
188    printf ("celtenc (CELT %s encoder)\n",CELT_VERSION);
189    printf ("Copyright (C) 2008 Jean-Marc Valin\n");
190 }
191
192 void version_short(void)
193 {
194    printf ("celtenc (CELT %s encoder)\n",CELT_VERSION);
195    printf ("Copyright (C) 2008 Jean-Marc Valin\n");
196 }
197
198 void usage(void)
199 {
200    printf ("Usage: celtenc [options] input_file output_file.oga\n");
201    printf ("\n");
202    printf ("Encodes input_file using CELT. It can read the WAV or raw files.\n");
203    printf ("\n");
204    printf ("input_file can be:\n");
205    printf ("  filename.wav      wav file\n");
206    printf ("  filename.*        Raw PCM file (any extension other than .wav)\n");
207    printf ("  -                 stdin\n");
208    printf ("\n");  
209    printf ("output_file can be:\n");
210    printf ("  filename.oga      compressed file\n");
211    printf ("  -                 stdout\n");
212    printf ("\n");  
213    printf ("Options:\n");
214    printf (" --bitrate n        Encoding bit-rate in kbit/sec\n"); 
215    printf (" --comp n           Encoding complexity (0-10)\n");
216    printf (" --framesize n      Frame size (Default: 256)\n");
217    printf (" --skeleton         Outputs ogg skeleton metadata (may cause incompatibilities)\n");
218    printf (" --comment          Add the given string as an extra comment. This may be\n");
219    printf ("                     used multiple times\n");
220    printf (" --author           Author of this track\n");
221    printf (" --title            Title for this track\n");
222    printf (" -h, --help         This help\n"); 
223    printf (" -v, --version      Version information\n"); 
224    printf (" -V                 Verbose mode (show bit-rate)\n"); 
225    printf ("Raw input options:\n");
226    printf (" --rate n           Sampling rate for raw input\n"); 
227    printf (" --mono             Consider raw input as mono\n"); 
228    printf (" --stereo           Consider raw input as stereo\n"); 
229    printf (" --le               Raw input is little-endian\n"); 
230    printf (" --be               Raw input is big-endian\n"); 
231    printf (" --8bit             Raw input is 8-bit unsigned\n"); 
232    printf (" --16bit            Raw input is 16-bit signed\n"); 
233    printf ("Default raw PCM input is 16-bit, little-endian, mono\n"); 
234 }
235
236
237 int main(int argc, char **argv)
238 {
239    int nb_samples, total_samples=0, nb_encoded;
240    int c;
241    int option_index = 0;
242    char *inFile, *outFile;
243    FILE *fin, *fout;
244    short input[MAX_FRAME_SIZE];
245    celt_int32_t frame_size = 256;
246    int quiet=0;
247    int nbBytes;
248    CELTMode *mode;
249    void *st;
250    unsigned char bits[MAX_FRAME_BYTES];
251    int with_skeleton = 0;
252    struct option long_options[] =
253    {
254       {"bitrate", required_argument, NULL, 0},
255       {"comp", required_argument, NULL, 0},
256       {"framesize", required_argument, NULL, 0},
257       {"skeleton",no_argument,NULL, 0},
258       {"help", no_argument, NULL, 0},
259       {"quiet", no_argument, NULL, 0},
260       {"le", no_argument, NULL, 0},
261       {"be", no_argument, NULL, 0},
262       {"8bit", no_argument, NULL, 0},
263       {"16bit", no_argument, NULL, 0},
264       {"mono", no_argument, NULL, 0},
265       {"stereo", no_argument, NULL, 0},
266       {"rate", required_argument, NULL, 0},
267       {"version", no_argument, NULL, 0},
268       {"version-short", no_argument, NULL, 0},
269       {"comment", required_argument, NULL, 0},
270       {"author", required_argument, NULL, 0},
271       {"title", required_argument, NULL, 0},
272       {0, 0, 0, 0}
273    };
274    int print_bitrate=0;
275    celt_int32_t rate=44100;
276    celt_int32_t size;
277    int chan=1;
278    int fmt=16;
279    int lsb=1;
280    ogg_stream_state os;
281    ogg_stream_state so; /* ogg stream for skeleton bitstream */
282    ogg_page              og;
283    ogg_packet            op;
284    int bytes_written=0, ret, result;
285    int id=-1;
286    CELTHeader header;
287    char vendor_string[64];
288    char *comments;
289    int comments_length;
290    int close_in=0, close_out=0;
291    int eos=0;
292    float bitrate=-1;
293    char first_bytes[12];
294    int wave_input=0;
295    celt_int32_t lookahead = 0;
296    int bytes_per_packet=48;
297    int complexity=-127;
298    int bitstream;
299
300
301    /*Process command-line options*/
302    while(1)
303    {
304       c = getopt_long (argc, argv, "hvV",
305                        long_options, &option_index);
306       if (c==-1)
307          break;
308       
309       switch(c)
310       {
311       case 0:
312          if (strcmp(long_options[option_index].name,"bitrate")==0)
313          {
314             bitrate = atof (optarg);
315          } else if (strcmp(long_options[option_index].name,"skeleton")==0)
316          {
317             with_skeleton=1;
318          } else if (strcmp(long_options[option_index].name,"help")==0)
319          {
320             usage();
321             exit(0);
322          } else if (strcmp(long_options[option_index].name,"quiet")==0)
323          {
324             quiet = 1;
325          } else if (strcmp(long_options[option_index].name,"version")==0)
326          {
327             version();
328             exit(0);
329          } else if (strcmp(long_options[option_index].name,"version-short")==0)
330          {
331             version_short();
332             exit(0);
333          } else if (strcmp(long_options[option_index].name,"le")==0)
334          {
335             lsb=1;
336          } else if (strcmp(long_options[option_index].name,"be")==0)
337          {
338             lsb=0;
339          } else if (strcmp(long_options[option_index].name,"8bit")==0)
340          {
341             fmt=8;
342          } else if (strcmp(long_options[option_index].name,"16bit")==0)
343          {
344             fmt=16;
345          } else if (strcmp(long_options[option_index].name,"stereo")==0)
346          {
347             chan=2;
348          } else if (strcmp(long_options[option_index].name,"mono")==0)
349          {
350             chan=1;
351          } else if (strcmp(long_options[option_index].name,"rate")==0)
352          {
353             rate=atoi (optarg);
354          } else if (strcmp(long_options[option_index].name,"comp")==0)
355          {
356             complexity=atoi (optarg);
357          } else if (strcmp(long_options[option_index].name,"framesize")==0)
358          {
359             frame_size=atoi (optarg);
360          } else if (strcmp(long_options[option_index].name,"comment")==0)
361          {
362            if (!strchr(optarg, '='))
363            {
364              fprintf (stderr, "Invalid comment: %s\n", optarg);
365              fprintf (stderr, "Comments must be of the form name=value\n");
366              exit(1);
367            }
368            comment_add(&comments, &comments_length, NULL, optarg); 
369          } else if (strcmp(long_options[option_index].name,"author")==0)
370          {
371            comment_add(&comments, &comments_length, "author=", optarg); 
372          } else if (strcmp(long_options[option_index].name,"title")==0)
373          {
374            comment_add(&comments, &comments_length, "title=", optarg); 
375          }
376
377          break;
378       case 'h':
379          usage();
380          exit(0);
381          break;
382       case 'v':
383          version();
384          exit(0);
385          break;
386       case 'V':
387          print_bitrate=1;
388          break;
389       case '?':
390          usage();
391          exit(1);
392          break;
393       }
394    }
395    if (argc-optind!=2)
396    {
397       usage();
398       exit(1);
399    }
400    inFile=argv[optind];
401    outFile=argv[optind+1];
402
403    /*Initialize Ogg stream struct*/
404    srand(time(NULL));
405    if (ogg_stream_init(&os, rand())==-1)
406    {
407       fprintf(stderr,"Error: stream init failed\n");
408       exit(1);
409    }
410    if (with_skeleton && ogg_stream_init(&so, rand())==-1)
411    {
412       fprintf(stderr,"Error: stream init failed\n");
413       exit(1);
414    }
415
416    if (strcmp(inFile, "-")==0)
417    {
418 #if defined WIN32 || defined _WIN32
419          _setmode(_fileno(stdin), _O_BINARY);
420 #elif defined OS2
421          _fsetmode(stdin,"b");
422 #endif
423       fin=stdin;
424    }
425    else 
426    {
427       fin = fopen(inFile, "rb");
428       if (!fin)
429       {
430          perror(inFile);
431          exit(1);
432       }
433       close_in=1;
434    }
435
436    {
437       fread(first_bytes, 1, 12, fin);
438       if (strncmp(first_bytes,"RIFF",4)==0 && strncmp(first_bytes,"RIFF",4)==0)
439       {
440          if (read_wav_header(fin, &rate, &chan, &fmt, &size)==-1)
441             exit(1);
442          wave_input=1;
443          lsb=1; /* CHECK: exists big-endian .wav ?? */
444       }
445    }
446
447    if (bitrate<0)
448      if (chan==1)
449        bitrate=64.0;
450      else
451        bitrate=128.0;
452    if (chan>2) {
453    } 
454      
455    bytes_per_packet = (bitrate*1000*frame_size/rate+4)/8;
456    
457    if (bytes_per_packet < 8) {
458       bytes_per_packet=8;
459       fprintf (stderr, "Warning: Requested bitrate (%0.3fkbit/sec) is too low. Setting CELT to 8 bytes/frame.\n",bitrate);
460    } else if (bytes_per_packet > 300) {
461       bytes_per_packet=300;
462       fprintf (stderr, "Warning: Requested bitrate (%0.3fkbit/sec) is too high. Setting CELT to 300 bytes/frame.\n",bitrate);      
463    }
464
465    bitrate = ((rate/(float)frame_size)*8*bytes_per_packet)/1000.0;
466
467    mode = celt_mode_create(rate, chan, frame_size, NULL);
468    if (!mode)
469       return 1;
470
471   celt_mode_info(mode,CELT_GET_BITSTREAM_VERSION,&bitstream);      
472
473    snprintf(vendor_string, sizeof(vendor_string), "Encoded with CELT %s (bitstream: %d)\n",CELT_VERSION,bitstream);
474    comment_init(&comments, &comments_length, vendor_string);
475
476    celt_mode_info(mode, CELT_GET_FRAME_SIZE, &frame_size);   
477    
478    celt_header_init(&header, mode);
479    header.nb_channels = chan;
480
481    {
482       char *st_string="mono";
483       if (chan==2)
484          st_string="stereo";
485       if (!quiet)
486          fprintf (stderr, "Encoding %d Hz %s audio in %d sample packets at %0.3fkbit/sec (%d bytes per packet) with bitstream version %d\n", 
487                header.sample_rate, st_string, frame_size, bitrate, bytes_per_packet,bitstream);
488    }
489
490    /*Initialize CELT encoder*/
491    st = celt_encoder_create(mode);
492
493    if (complexity!=-127) {
494      if (celt_encoder_ctl(st, CELT_SET_COMPLEXITY(complexity)) != CELT_OK)
495      {
496         fprintf (stderr, "Only complexity 0 through 10 is supported\n");
497         return 1;
498      }
499    }
500
501    if (strcmp(outFile,"-")==0)
502    {
503 #if defined WIN32 || defined _WIN32
504       _setmode(_fileno(stdout), _O_BINARY);
505 #endif
506       fout=stdout;
507    }
508    else 
509    {
510       fout = fopen(outFile, "wb");
511       if (!fout)
512       {
513          perror(outFile);
514          exit(1);
515       }
516       close_out=1;
517    }
518
519    if (with_skeleton) {
520       fprintf (stderr, "Warning: Enabling skeleton output may cause some decoders to fail.\n");
521    }
522
523    /* first packet should be the skeleton header. */
524    if (with_skeleton) {
525       add_fishead_packet(&so);
526       if ((ret = flush_ogg_stream_to_file(&so, fout))) {
527          fprintf (stderr,"Error: failed skeleton (fishead) header to output stream\n");
528          exit(1);
529       } else
530          bytes_written += ret;
531    }
532
533    /*Write header*/
534    {
535       unsigned char header_data[100];
536       int packet_size = celt_header_to_packet(&header, header_data, 100);
537       op.packet = header_data;
538       op.bytes = packet_size;
539       op.b_o_s = 1;
540       op.e_o_s = 0;
541       op.granulepos = 0;
542       op.packetno = 0;
543       ogg_stream_packetin(&os, &op);
544
545       while((result = ogg_stream_flush(&os, &og)))
546       {
547          if(!result) break;
548          ret = oe_write_page(&og, fout);
549          if(ret != og.header_len + og.body_len)
550          {
551             fprintf (stderr,"Error: failed writing header to output stream\n");
552             exit(1);
553          }
554          else
555             bytes_written += ret;
556       }
557
558       op.packet = (unsigned char *)comments;
559       op.bytes = comments_length;
560       op.b_o_s = 0;
561       op.e_o_s = 0;
562       op.granulepos = 0;
563       op.packetno = 1;
564       ogg_stream_packetin(&os, &op);
565    }
566
567    /* fisbone packet should be write after all bos pages */
568    if (with_skeleton) {
569       add_fisbone_packet(&so, os.serialno, &header);
570       if ((ret = flush_ogg_stream_to_file(&so, fout))) {
571          fprintf (stderr,"Error: failed writing skeleton (fisbone )header to output stream\n");
572          exit(1);
573       } else
574          bytes_written += ret;
575    }
576
577    /* writing the rest of the celt header packets */
578    while((result = ogg_stream_flush(&os, &og)))
579    {
580       if(!result) break;
581       ret = oe_write_page(&og, fout);
582       if(ret != og.header_len + og.body_len)
583       {
584          fprintf (stderr,"Error: failed writing header to output stream\n");
585          exit(1);
586       }
587       else
588          bytes_written += ret;
589    }
590
591    free(comments);
592
593    /* write the skeleton eos packet */
594    if (with_skeleton) {
595       add_eos_packet_to_stream(&so);
596       if ((ret = flush_ogg_stream_to_file(&so, fout))) {
597          fprintf (stderr,"Error: failed writing skeleton header to output stream\n");
598          exit(1);
599       } else
600          bytes_written += ret;
601    }
602
603
604    if (!wave_input)
605    {
606       nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, first_bytes, NULL);
607    } else {
608       nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, NULL, &size);
609    }
610    if (nb_samples==0)
611       eos=1;
612    total_samples += nb_samples;
613    nb_encoded = -lookahead;
614    /*Main encoding loop (one frame per iteration)*/
615    while (!eos || total_samples>nb_encoded)
616    {
617       id++;
618       /*Encode current frame*/
619
620       nbBytes = celt_encode(st, input, NULL, bits, bytes_per_packet);
621       if (nbBytes<0)
622       {
623          fprintf(stderr, "Got error %d while encoding. Aborting.\n", nbBytes);
624          break;
625       }
626       nb_encoded += frame_size;
627
628       if (wave_input)
629       {
630          nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, NULL, &size);
631       } else {
632          nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, NULL, NULL);
633       }
634       if (nb_samples==0)
635       {
636          eos=1;
637       }
638       if (eos && total_samples<=nb_encoded)
639          op.e_o_s = 1;
640       else
641          op.e_o_s = 0;
642       total_samples += nb_samples;
643
644       op.packet = (unsigned char *)bits;
645       op.bytes = nbBytes;
646       op.b_o_s = 0;
647       /*Is this redundent?*/
648       if (eos && total_samples<=nb_encoded)
649          op.e_o_s = 1;
650       else
651          op.e_o_s = 0;
652       op.granulepos = (id+1)*frame_size-lookahead;
653       if (op.granulepos>total_samples)
654          op.granulepos = total_samples;
655       /*printf ("granulepos: %d %d %d %d %d %d\n", (int)op.granulepos, id, nframes, lookahead, 5, 6);*/
656       op.packetno = 2+id;
657       ogg_stream_packetin(&os, &op);
658
659       /*Write all new pages (most likely 0 or 1)*/
660       while (ogg_stream_pageout(&os,&og))
661       {
662          ret = oe_write_page(&og, fout);
663          if(ret != og.header_len + og.body_len)
664          {
665             fprintf (stderr,"Error: failed writing header to output stream\n");
666             exit(1);
667          }
668          else
669             bytes_written += ret;
670       }
671    }
672    /*Flush all pages left to be written*/
673    while (ogg_stream_flush(&os, &og))
674    {
675       ret = oe_write_page(&og, fout);
676       if(ret != og.header_len + og.body_len)
677       {
678          fprintf (stderr,"Error: failed writing header to output stream\n");
679          exit(1);
680       }
681       else
682          bytes_written += ret;
683    }
684
685    celt_encoder_destroy(st);
686    celt_mode_destroy(mode);
687    ogg_stream_clear(&os);
688
689    if (close_in)
690       fclose(fin);
691    if (close_out)
692       fclose(fout);
693    return 0;
694 }
695
696 /*                 
697  Comments will be stored in the Vorbis style.            
698  It is describled in the "Structure" section of
699     http://www.xiph.org/ogg/vorbis/doc/v-comment.html
700
701 The comment header is decoded as follows:
702   1) [vendor_length] = read an unsigned integer of 32 bits
703   2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets
704   3) [user_comment_list_length] = read an unsigned integer of 32 bits
705   4) iterate [user_comment_list_length] times {
706      5) [length] = read an unsigned integer of 32 bits
707      6) this iteration's user comment = read a UTF-8 vector as [length] octets
708      }
709   7) [framing_bit] = read a single bit as boolean
710   8) if ( [framing_bit]  unset or end of packet ) then ERROR
711   9) done.
712
713   If you have troubles, please write to ymnk@jcraft.com.
714  */
715
716 #define readint(buf, base) (((buf[base+3]<<24)&0xff000000)| \
717                            ((buf[base+2]<<16)&0xff0000)| \
718                            ((buf[base+1]<<8)&0xff00)| \
719                             (buf[base]&0xff))
720 #define writeint(buf, base, val) do{ buf[base+3]=((val)>>24)&0xff; \
721                                      buf[base+2]=((val)>>16)&0xff; \
722                                      buf[base+1]=((val)>>8)&0xff; \
723                                      buf[base]=(val)&0xff; \
724                                  }while(0)
725
726 void comment_init(char **comments, int* length, char *vendor_string)
727 {
728   int vendor_length=strlen(vendor_string);
729   int user_comment_list_length=0;
730   int len=4+vendor_length+4;
731   char *p=(char*)malloc(len);
732   if(p==NULL){
733      fprintf (stderr, "malloc failed in comment_init()\n");
734      exit(1);
735   }
736   writeint(p, 0, vendor_length);
737   memcpy(p+4, vendor_string, vendor_length);
738   writeint(p, 4+vendor_length, user_comment_list_length);
739   *length=len;
740   *comments=p;
741 }
742 void comment_add(char **comments, int* length, char *tag, char *val)
743 {
744   char* p=*comments;
745   int vendor_length=readint(p, 0);
746   int user_comment_list_length=readint(p, 4+vendor_length);
747   int tag_len=(tag?strlen(tag):0);
748   int val_len=strlen(val);
749   int len=(*length)+4+tag_len+val_len;
750
751   p=(char*)realloc(p, len);
752   if(p==NULL){
753      fprintf (stderr, "realloc failed in comment_add()\n");
754      exit(1);
755   }
756
757   writeint(p, *length, tag_len+val_len);      /* length of comment */
758   if(tag) memcpy(p+*length+4, tag, tag_len);  /* comment */
759   memcpy(p+*length+4+tag_len, val, val_len);  /* comment */
760   writeint(p, 4+vendor_length, user_comment_list_length+1);
761
762   *comments=p;
763   *length=len;
764 }
765 #undef readint
766 #undef writeint