85053535f0cee4295df2840ff17f818af8dee096
[speexdsp.git] / src / speexenc.c
1 /* Copyright (C) 2002 Jean-Marc Valin 
2    File: speexenc.c
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8    
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13    
14    You should have received a copy of the GNU Lesser General Public
15    License along with this library; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18
19 #include <stdio.h>
20 #include <unistd.h>
21 #include <getopt.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25
26 #include "speex.h"
27 #include <ogg/ogg.h>
28 #include "wav_io.h"
29 #include "speex_header.h"
30 #include "misc.h"
31
32 /*Write an Ogg page to a file pointer*/
33 int oe_write_page(ogg_page *page, FILE *fp)
34 {
35    int written;
36    written = fwrite(page->header,1,page->header_len, fp);
37    written += fwrite(page->body,1,page->body_len, fp);
38    
39    return written;
40 }
41
42 #define MAX_FRAME_SIZE 2000
43 #define MAX_FRAME_BYTES 2000
44
45 /* Convert input audio bits, endians and channels */
46 int read_samples(FILE *fin,int frame_size, int bits, int channels, int lsb, float * input)
47 {   
48    unsigned char in[MAX_FRAME_BYTES*2];
49    int i,d;
50    short *s;
51
52    /*Read input audio*/
53    fread(in,bits/8*channels, frame_size, fin);
54    if (feof(fin))
55       return 1;
56    s=(short*)in;
57    if(bits==8)
58    {
59       /* Convert 8->16 bits */
60       for(i=frame_size*channels-1;i>=0;i--)
61       {
62          s[i]=(in[i]<<8)^0x8000;
63       }
64    } else
65    {
66       /* convert to our endian format */
67       for(i=0;i<frame_size*channels;i++)
68       {
69          if(lsb) 
70             s[i]=le_short(s[i]); 
71          else
72             s[i]=be_short(s[i]);
73       }
74    }
75
76    if(channels==2)
77    {
78       /* downmix to mono */
79       for(i=0;i<frame_size;i++)
80       {
81          d=s[i*2]+s[i*2+1];
82          s[i]=d>>1;
83       }
84    }
85
86    /* copy to float input buffer */
87    for (i=0;i<frame_size;i++)
88    {
89       input[i]=(short)s[i];
90    }
91
92    return 0;
93 }
94
95 void usage()
96 {
97    fprintf (stderr, "Speex encoder version " VERSION "\n");
98    fprintf (stderr, "\n");
99    fprintf (stderr, "usage: speexenc [options] input_file output_file\n");
100    fprintf (stderr, "\n");
101    fprintf (stderr, "input_file can be:\n");
102    fprintf (stderr, "  filename.wav          wav file\n");
103    fprintf (stderr, "  filename.*            raw PCM file (any extension other than .wav)\n");
104    fprintf (stderr, "  -                     stdin\n");
105    fprintf (stderr, "\n");  
106    fprintf (stderr, "output_file can be:\n");
107    fprintf (stderr, "  filename.spx          Speex file\n");
108    fprintf (stderr, "  -                     stdout\n");
109    fprintf (stderr, "\n");  
110    fprintf (stderr, "options:\n");
111    fprintf (stderr, "  --narrowband -n    Narrowband (8 kHz) input file\n"); 
112    fprintf (stderr, "  --wideband   -w    Wideband (16 kHz) input file\n"); 
113    fprintf (stderr, "  --quality n        Encoding quality (0-10), default 3\n"); 
114    fprintf (stderr, "  --lbr              Low bit-rate mode (equivalent to --quality 3)\n"); 
115    fprintf (stderr, "  --vbr              Enable variable bit-rate (VBR)\n"); 
116    fprintf (stderr, "  --comp n           Set encoding complexity (0-10), default 3\n"); 
117    fprintf (stderr, "  --nframes n        Number of frames per Ogg packet (1-10), default 1\n"); 
118    fprintf (stderr, "  --help       -h    This help\n"); 
119    fprintf (stderr, "  --version    -v    Version information\n"); 
120    fprintf (stderr, "  -V                 Verbose mode (show bit-rate)\n"); 
121    fprintf (stderr, "raw input options:\n");
122    fprintf (stderr, "  --le               Raw input is little-endian\n"); 
123    fprintf (stderr, "  --be               Raw input is big-endian\n"); 
124    fprintf (stderr, "  --8bit             Raw input is 8-bit unsigned\n"); 
125    fprintf (stderr, "  --16bit            Raw input is 16-bit signed\n"); 
126    fprintf (stderr, "\n");  
127    fprintf (stderr, "Default Raw PCM input is 16-bit, little-endian, mono\n"); 
128 }
129
130 void version()
131 {
132    fprintf (stderr, "Speex encoder version " VERSION "\n");
133 }
134
135 int main(int argc, char **argv)
136 {
137    int c;
138    int option_index = 0;
139    int narrowband=0, wideband=0;
140    char *inFile, *outFile;
141    FILE *fin, *fout;
142    float input[MAX_FRAME_SIZE];
143    int frame_size;
144    int vbr_enabled=0;
145    int nbBytes;
146    SpeexMode *mode=NULL;
147    void *st;
148    SpeexBits bits;
149    char cbits[MAX_FRAME_BYTES];
150    struct option long_options[] =
151    {
152       {"wideband", no_argument, NULL, 0},
153       {"narrowband", no_argument, NULL, 0},
154       {"lbr", no_argument, NULL, 0},
155       {"vbr", no_argument, NULL, 0},
156       {"quality", required_argument, NULL, 0},
157       {"nframes", required_argument, NULL, 0},
158       {"comp", required_argument, NULL, 0},
159       {"help", no_argument, NULL, 0},
160       {"le", no_argument, NULL, 0},
161       {"be", no_argument, NULL, 0},
162       {"lin8", no_argument, NULL, 0},
163       {"lin16", no_argument, NULL, 0},
164       {"version", no_argument, NULL, 0},
165       {0, 0, 0, 0}
166    };
167    int print_bitrate=0;
168    int rate, size;
169    int chan=1;
170    int fmt=16;
171    int quality=-1;
172    int lbr=0;
173    int lsb=1;
174    ogg_stream_state os;
175    ogg_page              og;
176    ogg_packet            op;
177    int bytes_written, ret, result;
178    int id=-1;
179    SpeexHeader header;
180    int nframes=1;
181    int complexity=3;
182    char *comments = "Encoded with Speex " VERSION;
183    int close_in=0, close_out=0;
184
185    /*Process command-line options*/
186    while(1)
187    {
188       c = getopt_long (argc, argv, "nwhvV",
189                        long_options, &option_index);
190       if (c==-1)
191          break;
192       
193       switch(c)
194       {
195       case 0:
196          if (strcmp(long_options[option_index].name,"narrowband")==0)
197             narrowband=1;
198          else if (strcmp(long_options[option_index].name,"wideband")==0)
199                wideband=1;
200          else if (strcmp(long_options[option_index].name,"lbr")==0)
201                lbr=1;
202          else if (strcmp(long_options[option_index].name,"vbr")==0)
203                vbr_enabled=1;
204          else if (strcmp(long_options[option_index].name,"quality")==0)
205          {
206             quality = atoi (optarg);
207          } else if (strcmp(long_options[option_index].name,"nframes")==0)
208          {
209             nframes = atoi (optarg);
210             if (nframes<1)
211                nframes=1;
212             if (nframes>10)
213                nframes=10;
214          } else if (strcmp(long_options[option_index].name,"comp")==0)
215          {
216             complexity = atoi (optarg);
217          } else if (strcmp(long_options[option_index].name,"help")==0)
218          {
219             usage();
220             exit(0);
221          } else if (strcmp(long_options[option_index].name,"version")==0)
222          {
223             version();
224             exit(0);
225          } else if (strcmp(long_options[option_index].name,"le")==0)
226          {
227             lsb=1;
228          } else if (strcmp(long_options[option_index].name,"be")==0)
229          {
230             lsb=0;
231          } else if (strcmp(long_options[option_index].name,"lin8")==0)
232          {
233             fmt=8;
234          } else if (strcmp(long_options[option_index].name,"lin16")==0)
235          {
236             fmt=16;
237          }
238          break;
239       case 'n':
240          narrowband=1;
241          break;
242       case 'h':
243          usage();
244          exit(0);
245          break;
246       case 'v':
247          version();
248          exit(0);
249          break;
250       case 'V':
251          print_bitrate=1;
252          break;
253       case 'w':
254          wideband=1;
255          break;
256       case '?':
257          usage();
258          exit(1);
259          break;
260       }
261    }
262    if (argc-optind!=2)
263    {
264       usage();
265       exit(1);
266    }
267    inFile=argv[optind];
268    outFile=argv[optind+1];
269
270    if (wideband && narrowband)
271    {
272       fprintf (stderr,"Cannot specify both wideband and narrowband at the same time\n");
273       exit(1);
274    };
275
276    /*Initialize Ogg stream struct*/
277    srand(time(NULL));
278    if (ogg_stream_init(&os, rand())==-1)
279    {
280       fprintf(stderr,"Stream init failed\n");
281       exit(1);
282    }
283
284    if (strcmp(inFile, "-")==0)
285       fin=stdin;
286    else 
287    {
288       fin = fopen(inFile, "r");
289       if (!fin)
290       {
291          perror(inFile);
292          exit(1);
293       }
294       close_in=1;
295    }
296
297    rate=0;
298    if (strcmp(inFile+strlen(inFile)-4,".wav")==0 || strcmp(inFile+strlen(inFile)-4,".WAV"))
299       {
300          if (read_wav_header(fin, &rate, &chan, &fmt, &size)==-1)
301             exit(1);
302          lsb=1; /* CHECK: exists big-endian .wav ?? */
303       }
304    /*fprintf (stderr, "wave info: %d %d %d %d\n", rate, chan, fmt, size);*/
305
306    if (rate==16000)
307    {
308       wideband=1;
309       if (narrowband)
310          fprintf (stderr,"Warning: encoding a wideband file in narrowband\n");
311    } else if (rate==8000)
312    {
313       narrowband=1;
314       if (wideband)
315          fprintf (stderr,"Warning: encoding a narrowband file in wideband\n");
316    }
317
318    if (!wideband)
319       narrowband=1;
320    if (narrowband)
321    {
322       if (!rate)
323          rate = 8000;
324       mode=&speex_nb_mode;
325    }
326    if (wideband)
327    {
328       if (!rate)
329          rate = 16000;
330       mode=&speex_wb_mode;
331    }
332
333    speex_init_header(&header, rate, 1, mode);
334    header.frames_per_packet=nframes;
335    header.vbr=vbr_enabled;
336
337    fprintf (stderr, "Encoding %d Hz audio using %s mode\n", 
338             header.rate, mode->modeName);
339    /*fprintf (stderr, "Encoding %d Hz audio at %d bps using %s mode\n", 
340      header.rate, mode->bitrate, mode->modeName);*/
341
342    /*Initialize Speex encoder*/
343    st = speex_encoder_init(mode);
344
345    if (strcmp(outFile,"-")==0)
346       fout=stdout;
347    else 
348    {
349       fout = fopen(outFile, "w");
350       if (!fout)
351       {
352          perror(outFile);
353          exit(1);
354       }
355       close_out=1;
356    }
357
358
359    /*Write header (format will change)*/
360    {
361
362       op.packet = (unsigned char *)speex_header_to_packet(&header, (int*)&(op.bytes));
363       op.b_o_s = 1;
364       op.e_o_s = 0;
365       op.granulepos = 0;
366       op.packetno = 0;
367       ogg_stream_packetin(&os, &op);
368       free(op.packet);
369
370       op.packet = (unsigned char *)comments;
371       op.bytes = strlen((char*)op.packet);
372       op.b_o_s = 0;
373       op.e_o_s = 0;
374       op.granulepos = 0;
375       op.packetno = 1;
376       ogg_stream_packetin(&os, &op);
377       
378       while((result = ogg_stream_flush(&os, &og)))
379       {
380          if(!result) break;
381          ret = oe_write_page(&og, fout);
382          if(ret != og.header_len + og.body_len)
383          {
384             fprintf (stderr,"Failed writing header to output stream\n");
385             exit(1);
386          }
387          else
388             bytes_written += ret;
389       }
390    }
391
392    speex_encoder_ctl(st, SPEEX_GET_FRAME_SIZE, &frame_size);
393    speex_encoder_ctl(st, SPEEX_SET_COMPLEXITY, &complexity);
394    if (vbr_enabled)
395    {
396       int tmp;
397       tmp=1;
398       speex_encoder_ctl(st, SPEEX_SET_VBR, &tmp);
399    }
400    if (lbr || quality != -1)
401    {
402       int tmp=quality;
403       if (quality==-1)
404          tmp = 3;
405       speex_encoder_ctl(st, SPEEX_SET_QUALITY, &tmp);
406       if (vbr_enabled)
407          speex_encoder_ctl(st, SPEEX_SET_VBR_QUALITY, &tmp);
408    }
409
410    speex_bits_init(&bits);
411    /*Main encoding loop (one frame per iteration)*/
412    while (1)
413    {
414       id++;
415       if (read_samples(fin,frame_size,fmt,chan,lsb,input))
416          break;
417       /*Encode current frame*/
418       speex_encode(st, input, &bits);
419       
420       if (print_bitrate) {
421          int tmp;
422          char ch=13;
423          speex_encoder_ctl(st, SPEEX_GET_BITRATE, &tmp);
424          fputc (ch, stderr);
425          fprintf (stderr, "Bitrate is use: %d bps     ", tmp);
426       }
427       if ((id+1)%nframes!=0)
428          continue;
429       nbBytes = speex_bits_write(&bits, cbits, MAX_FRAME_BYTES);
430       speex_bits_reset(&bits);
431       op.packet = (unsigned char *)cbits;
432       op.bytes = nbBytes;
433       op.b_o_s = 0;
434       op.e_o_s = 0;
435       op.granulepos = id*frame_size;
436       op.packetno = 2+id/nframes;
437       ogg_stream_packetin(&os, &op);
438
439       /*Write all new pages (not likely 0 or 1)*/
440       while (ogg_stream_pageout(&os,&og))
441       {
442          ret = oe_write_page(&og, fout);
443          if(ret != og.header_len + og.body_len)
444          {
445             fprintf (stderr,"Failed writing header to output stream\n");
446             exit(1);
447          }
448          else
449             bytes_written += ret;
450       }
451    }
452    
453    op.packet = (unsigned char *)"END OF STREAM";
454    op.bytes = 13;
455    op.b_o_s = 0;
456    op.e_o_s = 1;
457    op.granulepos = -1;
458    op.packetno = 3+id/nframes;
459    ogg_stream_packetin(&os, &op);
460    /*Flush all pages left to be written*/
461    while (ogg_stream_flush(&os, &og))
462    {
463       ret = oe_write_page(&og, fout);
464       if(ret != og.header_len + og.body_len)
465       {
466          fprintf (stderr,"Failed writing header to output stream\n");
467          exit(1);
468       }
469       else
470          bytes_written += ret;
471    }
472    
473
474    speex_encoder_destroy(st);
475    speex_bits_destroy(&bits);
476    ogg_stream_clear(&os);
477
478    if (close_in)
479       fclose(fin);
480    if (close_out)
481       fclose(fout);
482    return 1;
483 }
484