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