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