Integrated "ultra-wideband" with encoder/decoder.
[speexdsp.git] / src / speexenc.c
1 /* Copyright (C) 2002 Jean-Marc Valin 
2    File: speexenc.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 #include <stdio.h>
33 #if !defined WIN32 && !defined _WIN32
34 #include <unistd.h>
35 #include <getopt.h>
36 #endif
37 #include <stdlib.h>
38 #include <string.h>
39 #include <time.h>
40
41 #include "speex.h"
42 #include <ogg/ogg.h>
43 #include "wav_io.h"
44 #include "speex_header.h"
45 #include "misc.h"
46
47 #if defined WIN32 || defined _WIN32
48 #include "getopt_win.h"
49 /* We need the following two to set stdout to binary */
50 #include <io.h>
51 #include <fcntl.h>
52 #endif
53
54
55 void comment_init(char **comments, int* length, char *vendor_string);
56 void comment_add(char **comments, int* length, char *tag, char *val);
57
58
59 /*Write an Ogg page to a file pointer*/
60 int oe_write_page(ogg_page *page, FILE *fp)
61 {
62    int written;
63    written = fwrite(page->header,1,page->header_len, fp);
64    written += fwrite(page->body,1,page->body_len, fp);
65    
66    return written;
67 }
68
69 #define MAX_FRAME_SIZE 2000
70 #define MAX_FRAME_BYTES 2000
71
72 /* Convert input audio bits, endians and channels */
73 int read_samples(FILE *fin,int frame_size, int bits, int channels, int lsb, float * input)
74 {   
75    unsigned char in[MAX_FRAME_BYTES*2];
76    int i,d;
77    short *s;
78
79    /*Read input audio*/
80    fread(in,bits/8*channels, frame_size, fin);
81    if (feof(fin))
82       return 1;
83    s=(short*)in;
84    if(bits==8)
85    {
86       /* Convert 8->16 bits */
87       for(i=frame_size*channels-1;i>=0;i--)
88       {
89          s[i]=(in[i]<<8)^0x8000;
90       }
91    } else
92    {
93       /* convert to our endian format */
94       for(i=0;i<frame_size*channels;i++)
95       {
96          if(lsb) 
97             s[i]=le_short(s[i]); 
98          else
99             s[i]=be_short(s[i]);
100       }
101    }
102
103    if(channels==2)
104    {
105       /* downmix to mono */
106       for(i=0;i<frame_size;i++)
107       {
108          d=s[i*2]+s[i*2+1];
109          s[i]=d>>1;
110       }
111    }
112
113    /* copy to float input buffer */
114    for (i=0;i<frame_size;i++)
115    {
116       input[i]=(short)s[i];
117    }
118
119    return 0;
120 }
121
122 void usage()
123 {
124    /*printf ("Speex encoder version " VERSION " (compiled " __DATE__ ")\n");
125    printf ("\n");*/
126    printf ("Usage: speexenc [options] input_file output_file\n");
127    printf ("\n");
128    printf ("Encodes input_file using Speex. It can read the WAV or raw files.\n");
129    printf ("\n");
130    printf ("input_file can be:\n");
131    printf ("  filename.wav          wav file\n");
132    printf ("  filename.*            raw PCM file (any extension other than .wav)\n");
133    printf ("  -                     stdin\n");
134    printf ("\n");  
135    printf ("output_file can be:\n");
136    printf ("  filename.spx          Speex file\n");
137    printf ("  -                     stdout\n");
138    printf ("\n");  
139    printf ("Options:\n");
140    printf (" -n, --narrowband   Narrowband (8 kHz) input file\n"); 
141    printf (" -w, --wideband     Wideband (16 kHz) input file\n"); 
142    printf (" -u, --ultra-wideband \"Ultra-Wideband\" (32 kHz) input file\n"); 
143    printf (" --quality n        Encoding quality (0-10), default 3\n"); 
144    printf (" --vbr              Enable variable bit-rate (VBR)\n"); 
145    printf (" --comp n           Set encoding complexity (0-10), default 3\n"); 
146    printf (" --nframes n        Number of frames per Ogg packet (1-10), default 1\n"); 
147    printf (" --comment          Add the given string as an extra comment. This may be\n                     used multiple times.\n");
148    printf (" --author           Author of this track.\n");
149    printf (" --title            Title for this track.\n");
150    printf (" -h, --help         This help\n"); 
151    printf (" -v, --version      Version information\n"); 
152    printf (" -V                 Verbose mode (show bit-rate)\n"); 
153    printf ("Raw input options:\n");
154    printf (" --le               Raw input is little-endian\n"); 
155    printf (" --be               Raw input is big-endian\n"); 
156    printf (" --8bit             Raw input is 8-bit unsigned\n"); 
157    printf (" --16bit            Raw input is 16-bit signed\n"); 
158    printf ("\n");  
159    printf ("Default Raw PCM input is 16-bit, little-endian, mono\n"); 
160 }
161
162 void version()
163 {
164    printf ("Speex encoder version " VERSION " (compiled " __DATE__ ")\n");
165 }
166
167 int main(int argc, char **argv)
168 {
169    int c;
170    int option_index = 0;
171    int narrowband=0, wideband=0, ultrawide=0;
172    char *inFile, *outFile;
173    FILE *fin, *fout;
174    float input[MAX_FRAME_SIZE];
175    int frame_size;
176    int vbr_enabled=0;
177    int nbBytes;
178    SpeexMode *mode=NULL;
179    void *st;
180    SpeexBits bits;
181    char cbits[MAX_FRAME_BYTES];
182    struct option long_options[] =
183    {
184       {"wideband", no_argument, NULL, 0},
185       {"ultra-wideband", no_argument, NULL, 0},
186       {"narrowband", no_argument, NULL, 0},
187       {"vbr", no_argument, NULL, 0},
188       {"quality", required_argument, NULL, 0},
189       {"nframes", required_argument, NULL, 0},
190       {"comp", required_argument, NULL, 0},
191       {"help", no_argument, NULL, 0},
192       {"le", no_argument, NULL, 0},
193       {"be", no_argument, NULL, 0},
194       {"lin8", no_argument, NULL, 0},
195       {"lin16", no_argument, NULL, 0},
196       {"version", no_argument, NULL, 0},
197       {"comment", required_argument, NULL, 0},
198       {"author", required_argument, NULL, 0},
199       {"title", required_argument, NULL, 0},
200       {0, 0, 0, 0}
201    };
202    int print_bitrate=0;
203    int rate, size;
204    int chan=1;
205    int fmt=16;
206    int quality=-1;
207    float vbr_quality=-1;
208    int lsb=1;
209    ogg_stream_state os;
210    ogg_page              og;
211    ogg_packet            op;
212    int bytes_written, ret, result;
213    int id=-1;
214    SpeexHeader header;
215    int nframes=1;
216    int complexity=3;
217    char *vendor_string = "Encoded with Speex " VERSION;
218    char *comments;
219    int comments_length;
220    int close_in=0, close_out=0;
221    int eos=0;
222
223    comment_init(&comments, &comments_length, vendor_string);
224
225    /*Process command-line options*/
226    while(1)
227    {
228       c = getopt_long (argc, argv, "nwhvV",
229                        long_options, &option_index);
230       if (c==-1)
231          break;
232       
233       switch(c)
234       {
235       case 0:
236          if (strcmp(long_options[option_index].name,"narrowband")==0)
237             narrowband=1;
238          else if (strcmp(long_options[option_index].name,"wideband")==0)
239                wideband=1;
240          else if (strcmp(long_options[option_index].name,"ultra-wideband")==0)
241                ultrawide=1;
242          else if (strcmp(long_options[option_index].name,"vbr")==0)
243                vbr_enabled=1;
244          else if (strcmp(long_options[option_index].name,"quality")==0)
245          {
246             quality = atoi (optarg);
247             vbr_quality=atof(optarg);
248          } else if (strcmp(long_options[option_index].name,"nframes")==0)
249          {
250             nframes = atoi (optarg);
251             if (nframes<1)
252                nframes=1;
253             if (nframes>10)
254                nframes=10;
255          } else if (strcmp(long_options[option_index].name,"comp")==0)
256          {
257             complexity = atoi (optarg);
258          } else if (strcmp(long_options[option_index].name,"help")==0)
259          {
260             usage();
261             exit(0);
262          } else if (strcmp(long_options[option_index].name,"version")==0)
263          {
264             version();
265             exit(0);
266          } else if (strcmp(long_options[option_index].name,"le")==0)
267          {
268             lsb=1;
269          } else if (strcmp(long_options[option_index].name,"be")==0)
270          {
271             lsb=0;
272          } else if (strcmp(long_options[option_index].name,"lin8")==0)
273          {
274             fmt=8;
275          } else if (strcmp(long_options[option_index].name,"lin16")==0)
276          {
277             fmt=16;
278          } else if (strcmp(long_options[option_index].name,"comment")==0)
279          {
280            comment_add(&comments, &comments_length, NULL, optarg); 
281          } else if (strcmp(long_options[option_index].name,"author")==0)
282          {
283            comment_add(&comments, &comments_length, "author=", optarg); 
284          } else if (strcmp(long_options[option_index].name,"title")==0)
285          {
286            comment_add(&comments, &comments_length, "title=", optarg); 
287          }
288
289          break;
290       case 'n':
291          narrowband=1;
292          break;
293       case 'h':
294          usage();
295          exit(0);
296          break;
297       case 'v':
298          version();
299          exit(0);
300          break;
301       case 'V':
302          print_bitrate=1;
303          break;
304       case 'w':
305          wideband=1;
306          break;
307       case 'u':
308          ultrawide=1;
309          break;
310       case '?':
311          usage();
312          exit(1);
313          break;
314       }
315    }
316    if (argc-optind!=2)
317    {
318       usage();
319       exit(1);
320    }
321    inFile=argv[optind];
322    outFile=argv[optind+1];
323
324    if ((wideband && narrowband) || (wideband && ultrawide) || (ultrawide && narrowband))
325    {
326       fprintf (stderr,"Cannot specify two modes at the same time\n");
327       exit(1);
328    };
329
330    /*Initialize Ogg stream struct*/
331    srand(time(NULL));
332    if (ogg_stream_init(&os, rand())==-1)
333    {
334       fprintf(stderr,"Stream init failed\n");
335       exit(1);
336    }
337
338    if (strcmp(inFile, "-")==0)
339    {
340 #if defined WIN32 || defined _WIN32
341          _setmode(_fileno(stdin), _O_BINARY);
342 #endif
343       fin=stdin;
344    }
345    else 
346    {
347 #if defined WIN32 || defined _WIN32
348       fin = fopen(inFile, "rb");
349 #else
350       fin = fopen(inFile, "r");
351 #endif
352       if (!fin)
353       {
354          perror(inFile);
355          exit(1);
356       }
357       close_in=1;
358    }
359
360    rate=0;
361    if (strcmp(inFile+strlen(inFile)-4,".wav")==0 || strcmp(inFile+strlen(inFile)-4,".WAV")==0)
362       {
363          if (read_wav_header(fin, &rate, &chan, &fmt, &size)==-1)
364             exit(1);
365          lsb=1; /* CHECK: exists big-endian .wav ?? */
366       }
367    /*fprintf (stderr, "wave info: %d %d %d %d\n", rate, chan, fmt, size);*/
368
369    if (rate==16000)
370    {
371       wideband=1;
372       if (narrowband)
373          fprintf (stderr,"Warning: encoding a wideband file in narrowband\n");
374    } else if (rate==8000)
375    {
376       narrowband=1;
377       if (wideband)
378          fprintf (stderr,"Warning: encoding a narrowband file in wideband\n");
379    } else if (rate==22050)
380    {
381       wideband=1;
382       fprintf (stderr,"Warning: Speex is not optimized for 22.05 kHz sampling rate. Your mileage may vary\n");
383       if (narrowband)
384          fprintf (stderr,"Warning: encoding a wideband file in narrowband\n");
385    } else if (rate==32000)
386    {
387       ultrawide=1;
388       if (wideband)
389          fprintf (stderr,"Warning: encoding a narrowband file in wideband\n");
390    } else if (rate==44100)
391    {
392       fprintf (stderr,"Warning: Speex is not optimized for 44.1 kHz sampling rate. Your mileage may vary\n");
393       ultrawide=1;
394       if (wideband)
395          fprintf (stderr,"Warning: encoding a narrowband file in wideband\n");
396    } else if (rate==48000)
397    {
398       fprintf (stderr,"Warning: Speex is not optimized for 48 kHz sampling rate. Your mileage may vary\n");
399       ultrawide=1;
400       if (wideband)
401          fprintf (stderr,"Warning: encoding a narrowband file in wideband\n");
402    } else if (rate==11025)
403    {
404       fprintf (stderr,"Warning: Speex is not optimized for 11.025 kHz sampling rate. Your mileage may vary\n");
405       narrowband=1;
406       if (wideband)
407          fprintf (stderr,"Warning: encoding a narrowband file in wideband\n");
408    }
409
410    if (!wideband)
411       narrowband=1;
412    if (narrowband)
413    {
414       if (!rate)
415          rate = 8000;
416       mode=&speex_nb_mode;
417    }
418    if (wideband)
419    {
420       if (!rate)
421          rate = 16000;
422       mode=&speex_wb_mode;
423    }
424    if (ultrawide)
425    {
426       if (!rate)
427          rate = 32000;
428       mode=&speex_uwb_mode;
429    }
430
431    speex_init_header(&header, rate, 1, mode);
432    header.frames_per_packet=nframes;
433    header.vbr=vbr_enabled;
434
435    fprintf (stderr, "Encoding %d Hz audio using %s mode\n", 
436             header.rate, mode->modeName);
437    /*fprintf (stderr, "Encoding %d Hz audio at %d bps using %s mode\n", 
438      header.rate, mode->bitrate, mode->modeName);*/
439
440    /*Initialize Speex encoder*/
441    st = speex_encoder_init(mode);
442
443    if (strcmp(outFile,"-")==0)
444    {
445 #if defined WIN32 || defined _WIN32
446       _setmode(_fileno(stdout), _O_BINARY);
447 #endif
448       fout=stdout;
449    }
450    else 
451    {
452 #if defined WIN32 || defined _WIN32
453       fout = fopen(outFile, "wb");
454 #else
455       fout = fopen(outFile, "w");
456 #endif
457       if (!fout)
458       {
459          perror(outFile);
460          exit(1);
461       }
462       close_out=1;
463    }
464
465
466    /*Write header (format will change)*/
467    {
468
469       op.packet = (unsigned char *)speex_header_to_packet(&header, (int*)&(op.bytes));
470       op.b_o_s = 1;
471       op.e_o_s = 0;
472       op.granulepos = 0;
473       op.packetno = 0;
474       ogg_stream_packetin(&os, &op);
475       free(op.packet);
476
477       op.packet = (unsigned char *)comments;
478       op.bytes = comments_length;
479       op.b_o_s = 0;
480       op.e_o_s = 0;
481       op.granulepos = 0;
482       op.packetno = 1;
483       ogg_stream_packetin(&os, &op);
484       
485       while((result = ogg_stream_flush(&os, &og)))
486       {
487          if(!result) break;
488          ret = oe_write_page(&og, fout);
489          if(ret != og.header_len + og.body_len)
490          {
491             fprintf (stderr,"Failed writing header to output stream\n");
492             exit(1);
493          }
494          else
495             bytes_written += ret;
496       }
497    }
498
499    free(comments);
500
501    speex_encoder_ctl(st, SPEEX_GET_FRAME_SIZE, &frame_size);
502    speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &complexity);
503    if (vbr_enabled)
504    {
505       int tmp;
506       tmp=1;
507       speex_encoder_ctl(st, SPEEX_SET_VBR, &tmp);
508    }
509    if (quality >= 0)
510    {
511       if (vbr_enabled)
512          speex_encoder_ctl(st, SPEEX_SET_VBR_QUALITY, &vbr_quality);
513       else
514          speex_encoder_ctl(st, SPEEX_SET_QUALITY, &quality);
515    }
516
517    speex_bits_init(&bits);
518
519    if (read_samples(fin,frame_size,fmt,chan,lsb,input))
520       eos=1;
521
522    /*Main encoding loop (one frame per iteration)*/
523    while (!eos)
524    {
525       id++;
526       /*Encode current frame*/
527       speex_encode(st, input, &bits);
528       
529       if (print_bitrate) {
530          int tmp;
531          char ch=13;
532          speex_encoder_ctl(st, SPEEX_GET_BITRATE, &tmp);
533          fputc (ch, stderr);
534          fprintf (stderr, "Bitrate is use: %d bps     ", tmp);
535       }
536
537       if (read_samples(fin,frame_size,fmt,chan,lsb,input))
538       {
539          eos=1;
540          op.e_o_s = 1;
541       }
542
543       if ((id+1)%nframes!=0)
544          continue;
545       nbBytes = speex_bits_write(&bits, cbits, MAX_FRAME_BYTES);
546       speex_bits_reset(&bits);
547       op.packet = (unsigned char *)cbits;
548       op.bytes = nbBytes;
549       op.b_o_s = 0;
550       if (eos)
551          op.e_o_s = 1;
552       else
553          op.e_o_s = 0;
554       op.granulepos = (id+nframes)*frame_size;
555       op.packetno = 2+id/nframes;
556       ogg_stream_packetin(&os, &op);
557
558       /*Write all new pages (most likely 0 or 1)*/
559       while (ogg_stream_pageout(&os,&og))
560       {
561          ret = oe_write_page(&og, fout);
562          if(ret != og.header_len + og.body_len)
563          {
564             fprintf (stderr,"Failed writing header to output stream\n");
565             exit(1);
566          }
567          else
568             bytes_written += ret;
569       }
570    }
571    if ((id+1)%nframes!=0)
572    {
573       while ((id+1)%nframes!=0)
574       {
575          id++;
576          speex_bits_pack(&bits, 0, 7);
577       }
578       nbBytes = speex_bits_write(&bits, cbits, MAX_FRAME_BYTES);
579       op.packet = (unsigned char *)cbits;
580       op.bytes = nbBytes;
581       op.b_o_s = 0;
582       op.e_o_s = 1;
583       op.granulepos = (id+nframes)*frame_size;
584       op.packetno = 2+id/nframes;
585       ogg_stream_packetin(&os, &op);
586    }
587    /*Flush all pages left to be written*/
588    while (ogg_stream_flush(&os, &og))
589    {
590       ret = oe_write_page(&og, fout);
591       if(ret != og.header_len + og.body_len)
592       {
593          fprintf (stderr,"Failed writing header to output stream\n");
594          exit(1);
595       }
596       else
597          bytes_written += ret;
598    }
599    
600
601    speex_encoder_destroy(st);
602    speex_bits_destroy(&bits);
603    ogg_stream_clear(&os);
604
605    if (close_in)
606       fclose(fin);
607    if (close_out)
608       fclose(fout);
609    return 1;
610 }
611
612 /*                 
613  Comments will be stored in the Vorbis style.            
614  It is describled in the "Structure" section of
615     http://www.xiph.org/ogg/vorbis/doc/v-comment.html
616
617 The comment header is decoded as follows:
618   1) [vendor_length] = read an unsigned integer of 32 bits
619   2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets
620   3) [user_comment_list_length] = read an unsigned integer of 32 bits
621   4) iterate [user_comment_list_length] times {
622      5) [length] = read an unsigned integer of 32 bits
623      6) this iteration's user comment = read a UTF-8 vector as [length] octets
624      }
625   7) [framing_bit] = read a single bit as boolean
626   8) if ( [framing_bit]  unset or end of packet ) then ERROR
627   9) done.
628
629   If you have troubles, please write to ymnk@jcraft.com.
630  */
631
632 #define readint(buf, base) (((buf[base+3]<<24)&0xff000000)| \
633                            ((buf[base+2]<<16)&0xff0000)| \
634                            ((buf[base+1]<<8)&0xff00)| \
635                             (buf[base]&0xff))
636 #define writeint(buf, base, val) do{ buf[base+3]=((val)>>24)&0xff; \
637                                      buf[base+2]=((val)>>16)&0xff; \
638                                      buf[base+1]=((val)>>8)&0xff; \
639                                      buf[base]=(val)&0xff; \
640                                  }while(0)
641
642 void comment_init(char **comments, int* length, char *vendor_string)
643 {
644   int vendor_length=strlen(vendor_string);
645   int user_comment_list_length=0;
646   int len=4+vendor_length+4;
647   char *p=(char*)malloc(len);
648   if(p==NULL){
649   }
650   writeint(p, 0, vendor_length);
651   memcpy(p+4, vendor_string, vendor_length);
652   writeint(p, 4+vendor_length, user_comment_list_length);
653   *length=len;
654   *comments=p;
655 }
656 void comment_add(char **comments, int* length, char *tag, char *val)
657 {
658   char* p=*comments;
659   int vendor_length=readint(p, 0);
660   int user_comment_list_length=readint(p, 4+vendor_length);
661   int tag_len=(tag?strlen(tag):0);
662   int val_len=strlen(val);
663   int len=(*length)+4+tag_len+val_len;
664
665   p=(char*)realloc(p, len);
666   if(p==NULL){
667   }
668
669   writeint(p, *length, tag_len+val_len);      /* length of comment */
670   if(tag) memcpy(p+*length+4, tag, tag_len);  /* comment */
671   memcpy(p+*length+4+tag_len, val, val_len);  /* comment */
672   writeint(p, 4+vendor_length, user_comment_list_length+1);
673
674   *comments=p;
675   *length=len;
676 }
677 #undef readint
678 #undef writeint