Some additional documentaion in the celtenc UI.
[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 encoder)\n");
189    printf ("Copyright (C) 2008 Jean-Marc Valin\n");
190 }
191
192 void version_short(void)
193 {
194    printf ("celtenc (CELT encoder)\n");
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    
299    snprintf(vendor_string, sizeof(vendor_string), "Encoded with CELT\n");
300    
301    comment_init(&comments, &comments_length, vendor_string);
302
303    /*Process command-line options*/
304    while(1)
305    {
306       c = getopt_long (argc, argv, "hvV",
307                        long_options, &option_index);
308       if (c==-1)
309          break;
310       
311       switch(c)
312       {
313       case 0:
314          if (strcmp(long_options[option_index].name,"bitrate")==0)
315          {
316             bitrate = atof (optarg);
317          } else if (strcmp(long_options[option_index].name,"skeleton")==0)
318          {
319             with_skeleton=1;
320          } else if (strcmp(long_options[option_index].name,"help")==0)
321          {
322             usage();
323             exit(0);
324          } else if (strcmp(long_options[option_index].name,"quiet")==0)
325          {
326             quiet = 1;
327          } else if (strcmp(long_options[option_index].name,"version")==0)
328          {
329             version();
330             exit(0);
331          } else if (strcmp(long_options[option_index].name,"version-short")==0)
332          {
333             version_short();
334             exit(0);
335          } else if (strcmp(long_options[option_index].name,"le")==0)
336          {
337             lsb=1;
338          } else if (strcmp(long_options[option_index].name,"be")==0)
339          {
340             lsb=0;
341          } else if (strcmp(long_options[option_index].name,"8bit")==0)
342          {
343             fmt=8;
344          } else if (strcmp(long_options[option_index].name,"16bit")==0)
345          {
346             fmt=16;
347          } else if (strcmp(long_options[option_index].name,"stereo")==0)
348          {
349             chan=2;
350          } else if (strcmp(long_options[option_index].name,"mono")==0)
351          {
352             chan=1;
353          } else if (strcmp(long_options[option_index].name,"rate")==0)
354          {
355             rate=atoi (optarg);
356          } else if (strcmp(long_options[option_index].name,"comp")==0)
357          {
358             complexity=atoi (optarg);
359          } else if (strcmp(long_options[option_index].name,"framesize")==0)
360          {
361             frame_size=atoi (optarg);
362          } else if (strcmp(long_options[option_index].name,"comment")==0)
363          {
364            if (!strchr(optarg, '='))
365            {
366              fprintf (stderr, "Invalid comment: %s\n", optarg);
367              fprintf (stderr, "Comments must be of the form name=value\n");
368              exit(1);
369            }
370            comment_add(&comments, &comments_length, NULL, optarg); 
371          } else if (strcmp(long_options[option_index].name,"author")==0)
372          {
373            comment_add(&comments, &comments_length, "author=", optarg); 
374          } else if (strcmp(long_options[option_index].name,"title")==0)
375          {
376            comment_add(&comments, &comments_length, "title=", optarg); 
377          }
378
379          break;
380       case 'h':
381          usage();
382          exit(0);
383          break;
384       case 'v':
385          version();
386          exit(0);
387          break;
388       case 'V':
389          print_bitrate=1;
390          break;
391       case '?':
392          usage();
393          exit(1);
394          break;
395       }
396    }
397    if (argc-optind!=2)
398    {
399       usage();
400       exit(1);
401    }
402    inFile=argv[optind];
403    outFile=argv[optind+1];
404
405    /*Initialize Ogg stream struct*/
406    srand(time(NULL));
407    if (ogg_stream_init(&os, rand())==-1)
408    {
409       fprintf(stderr,"Error: stream init failed\n");
410       exit(1);
411    }
412    if (with_skeleton && ogg_stream_init(&so, rand())==-1)
413    {
414       fprintf(stderr,"Error: stream init failed\n");
415       exit(1);
416    }
417
418    if (strcmp(inFile, "-")==0)
419    {
420 #if defined WIN32 || defined _WIN32
421          _setmode(_fileno(stdin), _O_BINARY);
422 #elif defined OS2
423          _fsetmode(stdin,"b");
424 #endif
425       fin=stdin;
426    }
427    else 
428    {
429       fin = fopen(inFile, "rb");
430       if (!fin)
431       {
432          perror(inFile);
433          exit(1);
434       }
435       close_in=1;
436    }
437
438    {
439       fread(first_bytes, 1, 12, fin);
440       if (strncmp(first_bytes,"RIFF",4)==0 && strncmp(first_bytes,"RIFF",4)==0)
441       {
442          if (read_wav_header(fin, &rate, &chan, &fmt, &size)==-1)
443             exit(1);
444          wave_input=1;
445          lsb=1; /* CHECK: exists big-endian .wav ?? */
446       }
447    }
448
449    if (bitrate<0)
450      if (chan==1)
451        bitrate=64.0;
452      else
453        bitrate=128.0;
454    if (chan>2) {
455    } 
456      
457    bytes_per_packet = (bitrate*1000*frame_size/rate+4)/8;
458    
459    if (bytes_per_packet < 8) {
460       bytes_per_packet=8;
461       fprintf (stderr, "Warning: Requested bitrate (%0.3fkbit/sec) is too low. Setting CELT to 8 bytes/frame.\n",bitrate);
462    } else if (bytes_per_packet > 300) {
463       bytes_per_packet=300;
464       fprintf (stderr, "Warning: Requested bitrate (%0.3fkbit/sec) is too high. Setting CELT to 300 bytes/frame.\n",bitrate);      
465    }
466
467    bitrate = ((rate/(float)frame_size)*8*bytes_per_packet)/1000.0;
468
469    mode = celt_mode_create(rate, chan, frame_size, NULL);
470    if (!mode)
471       return 1;
472    celt_mode_info(mode, CELT_GET_FRAME_SIZE, &frame_size);   
473    
474    celt_header_init(&header, mode);
475    header.nb_channels = chan;
476
477    {
478       char *st_string="mono";
479       if (chan==2)
480          st_string="stereo";
481       if (!quiet)
482          fprintf (stderr, "Encoding %d Hz %s audio in %d sample packets at %0.3fkbit/sec (%d bytes per packet)\n", 
483                header.sample_rate, st_string, frame_size, bitrate, bytes_per_packet);
484    }
485
486    /*Initialize CELT encoder*/
487    st = celt_encoder_create(mode);
488
489    if (complexity!=-127) {
490      if (celt_encoder_ctl(st, CELT_SET_COMPLEXITY(complexity)) != CELT_OK)
491      {
492         fprintf (stderr, "Only complexity 0 through 10 is supported\n");
493         return 1;
494      }
495    }
496
497    if (strcmp(outFile,"-")==0)
498    {
499 #if defined WIN32 || defined _WIN32
500       _setmode(_fileno(stdout), _O_BINARY);
501 #endif
502       fout=stdout;
503    }
504    else 
505    {
506       fout = fopen(outFile, "wb");
507       if (!fout)
508       {
509          perror(outFile);
510          exit(1);
511       }
512       close_out=1;
513    }
514
515    if (with_skeleton) {
516       fprintf (stderr, "Warning: Enabling skeleton output may cause some decoders to fail.\n");
517    }
518
519    /* first packet should be the skeleton header. */
520    if (with_skeleton) {
521       add_fishead_packet(&so);
522       if ((ret = flush_ogg_stream_to_file(&so, fout))) {
523          fprintf (stderr,"Error: failed skeleton (fishead) header to output stream\n");
524          exit(1);
525       } else
526          bytes_written += ret;
527    }
528
529    /*Write header*/
530    {
531       unsigned char header_data[100];
532       int packet_size = celt_header_to_packet(&header, header_data, 100);
533       op.packet = header_data;
534       op.bytes = packet_size;
535       op.b_o_s = 1;
536       op.e_o_s = 0;
537       op.granulepos = 0;
538       op.packetno = 0;
539       ogg_stream_packetin(&os, &op);
540
541       while((result = ogg_stream_flush(&os, &og)))
542       {
543          if(!result) break;
544          ret = oe_write_page(&og, fout);
545          if(ret != og.header_len + og.body_len)
546          {
547             fprintf (stderr,"Error: failed writing header to output stream\n");
548             exit(1);
549          }
550          else
551             bytes_written += ret;
552       }
553
554       op.packet = (unsigned char *)comments;
555       op.bytes = comments_length;
556       op.b_o_s = 0;
557       op.e_o_s = 0;
558       op.granulepos = 0;
559       op.packetno = 1;
560       ogg_stream_packetin(&os, &op);
561    }
562
563    /* fisbone packet should be write after all bos pages */
564    if (with_skeleton) {
565       add_fisbone_packet(&so, os.serialno, &header);
566       if ((ret = flush_ogg_stream_to_file(&so, fout))) {
567          fprintf (stderr,"Error: failed writing skeleton (fisbone )header to output stream\n");
568          exit(1);
569       } else
570          bytes_written += ret;
571    }
572
573    /* writing the rest of the celt header packets */
574    while((result = ogg_stream_flush(&os, &og)))
575    {
576       if(!result) break;
577       ret = oe_write_page(&og, fout);
578       if(ret != og.header_len + og.body_len)
579       {
580          fprintf (stderr,"Error: failed writing header to output stream\n");
581          exit(1);
582       }
583       else
584          bytes_written += ret;
585    }
586
587    free(comments);
588
589    /* write the skeleton eos packet */
590    if (with_skeleton) {
591       add_eos_packet_to_stream(&so);
592       if ((ret = flush_ogg_stream_to_file(&so, fout))) {
593          fprintf (stderr,"Error: failed writing skeleton header to output stream\n");
594          exit(1);
595       } else
596          bytes_written += ret;
597    }
598
599
600    if (!wave_input)
601    {
602       nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, first_bytes, NULL);
603    } else {
604       nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, NULL, &size);
605    }
606    if (nb_samples==0)
607       eos=1;
608    total_samples += nb_samples;
609    nb_encoded = -lookahead;
610    /*Main encoding loop (one frame per iteration)*/
611    while (!eos || total_samples>nb_encoded)
612    {
613       id++;
614       /*Encode current frame*/
615
616       nbBytes = celt_encode(st, input, NULL, bits, bytes_per_packet);
617       if (nbBytes<0)
618       {
619          fprintf(stderr, "Got error %d while encoding. Aborting.\n", nbBytes);
620          break;
621       }
622       nb_encoded += frame_size;
623
624       if (wave_input)
625       {
626          nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, NULL, &size);
627       } else {
628          nb_samples = read_samples(fin,frame_size,fmt,chan,lsb,input, NULL, NULL);
629       }
630       if (nb_samples==0)
631       {
632          eos=1;
633       }
634       if (eos && total_samples<=nb_encoded)
635          op.e_o_s = 1;
636       else
637          op.e_o_s = 0;
638       total_samples += nb_samples;
639
640       op.packet = (unsigned char *)bits;
641       op.bytes = nbBytes;
642       op.b_o_s = 0;
643       /*Is this redundent?*/
644       if (eos && total_samples<=nb_encoded)
645          op.e_o_s = 1;
646       else
647          op.e_o_s = 0;
648       op.granulepos = (id+1)*frame_size-lookahead;
649       if (op.granulepos>total_samples)
650          op.granulepos = total_samples;
651       /*printf ("granulepos: %d %d %d %d %d %d\n", (int)op.granulepos, id, nframes, lookahead, 5, 6);*/
652       op.packetno = 2+id;
653       ogg_stream_packetin(&os, &op);
654
655       /*Write all new pages (most likely 0 or 1)*/
656       while (ogg_stream_pageout(&os,&og))
657       {
658          ret = oe_write_page(&og, fout);
659          if(ret != og.header_len + og.body_len)
660          {
661             fprintf (stderr,"Error: failed writing header to output stream\n");
662             exit(1);
663          }
664          else
665             bytes_written += ret;
666       }
667    }
668    /*Flush all pages left to be written*/
669    while (ogg_stream_flush(&os, &og))
670    {
671       ret = oe_write_page(&og, fout);
672       if(ret != og.header_len + og.body_len)
673       {
674          fprintf (stderr,"Error: failed writing header to output stream\n");
675          exit(1);
676       }
677       else
678          bytes_written += ret;
679    }
680
681    celt_encoder_destroy(st);
682    celt_mode_destroy(mode);
683    ogg_stream_clear(&os);
684
685    if (close_in)
686       fclose(fin);
687    if (close_out)
688       fclose(fout);
689    return 0;
690 }
691
692 /*                 
693  Comments will be stored in the Vorbis style.            
694  It is describled in the "Structure" section of
695     http://www.xiph.org/ogg/vorbis/doc/v-comment.html
696
697 The comment header is decoded as follows:
698   1) [vendor_length] = read an unsigned integer of 32 bits
699   2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets
700   3) [user_comment_list_length] = read an unsigned integer of 32 bits
701   4) iterate [user_comment_list_length] times {
702      5) [length] = read an unsigned integer of 32 bits
703      6) this iteration's user comment = read a UTF-8 vector as [length] octets
704      }
705   7) [framing_bit] = read a single bit as boolean
706   8) if ( [framing_bit]  unset or end of packet ) then ERROR
707   9) done.
708
709   If you have troubles, please write to ymnk@jcraft.com.
710  */
711
712 #define readint(buf, base) (((buf[base+3]<<24)&0xff000000)| \
713                            ((buf[base+2]<<16)&0xff0000)| \
714                            ((buf[base+1]<<8)&0xff00)| \
715                             (buf[base]&0xff))
716 #define writeint(buf, base, val) do{ buf[base+3]=((val)>>24)&0xff; \
717                                      buf[base+2]=((val)>>16)&0xff; \
718                                      buf[base+1]=((val)>>8)&0xff; \
719                                      buf[base]=(val)&0xff; \
720                                  }while(0)
721
722 void comment_init(char **comments, int* length, char *vendor_string)
723 {
724   int vendor_length=strlen(vendor_string);
725   int user_comment_list_length=0;
726   int len=4+vendor_length+4;
727   char *p=(char*)malloc(len);
728   if(p==NULL){
729      fprintf (stderr, "malloc failed in comment_init()\n");
730      exit(1);
731   }
732   writeint(p, 0, vendor_length);
733   memcpy(p+4, vendor_string, vendor_length);
734   writeint(p, 4+vendor_length, user_comment_list_length);
735   *length=len;
736   *comments=p;
737 }
738 void comment_add(char **comments, int* length, char *tag, char *val)
739 {
740   char* p=*comments;
741   int vendor_length=readint(p, 0);
742   int user_comment_list_length=readint(p, 4+vendor_length);
743   int tag_len=(tag?strlen(tag):0);
744   int val_len=strlen(val);
745   int len=(*length)+4+tag_len+val_len;
746
747   p=(char*)realloc(p, len);
748   if(p==NULL){
749      fprintf (stderr, "realloc failed in comment_add()\n");
750      exit(1);
751   }
752
753   writeint(p, *length, tag_len+val_len);      /* length of comment */
754   if(tag) memcpy(p+*length+4, tag, tag_len);  /* comment */
755   memcpy(p+*length+4+tag_len, val, val_len);  /* comment */
756   writeint(p, 4+vendor_length, user_comment_list_length+1);
757
758   *comments=p;
759   *length=len;
760 }
761 #undef readint
762 #undef writeint