Patch from Atsuhiko Yamanaka (speexenc.c only) to allow Vorbis-style
[speexdsp.git] / src / speexdec.c
1 /* Copyright (C) 2002 Jean-Marc Valin 
2    File: speexdec.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 #if !defined WIN32 && !defined _WIN32
21 #include <unistd.h>
22 #include <getopt.h>
23 #endif
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "speex.h"
28 #include "ogg/ogg.h"
29
30 #if defined WIN32 || defined _WIN32
31 #include <windows.h>
32 #include "getopt_win.h"
33 #include "wave_out.h"
34 /* We need the following two to set stdout to binary */
35 #include <io.h>
36 #include <fcntl.h>
37 #endif
38
39 #ifdef HAVE_SYS_SOUNDCARD_H
40 #include <sys/soundcard.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <fcntl.h>
44 #include <sys/ioctl.h>
45 #endif
46
47 #include <string.h>
48 #include "wav_io.h"
49 #include "speex_header.h"
50 #include "misc.h"
51
52 #define MAX_FRAME_SIZE 2000
53
54 #define readint(buf, base) ((buf[base+3]<<24)&0xff000000| \
55                            (buf[base+2]<<16)&0xff0000| \
56                            (buf[base+1]<<8)&0xff00| \
57                             buf[base]&0xff)
58
59 static void print_comments(char *comments, int length)
60 {
61    char *c=comments;
62    int len, i, nb_fields;
63
64    len=readint(c, 0);
65    c+=4;
66    fwrite(c, 1, len, stderr);
67    c+=len;
68    fprintf (stderr, "\n");
69    nb_fields=readint(c, 0);
70    c+=4;
71    for (i=0;i<nb_fields;i++)
72    {
73       len=readint(c, 0);
74       c+=4;
75       fwrite(c, 1, len, stderr);
76       c+=len;
77       fprintf (stderr, "\n");
78    }
79 }
80
81 FILE *out_file_open(char *outFile, int rate)
82 {
83    FILE *fout;
84    /*Open output file*/
85    if (strlen(outFile)==0)
86    {
87 #if defined HAVE_SYS_SOUNDCARD_H
88       int audio_fd, format, stereo;
89       audio_fd=open("/dev/dsp", O_WRONLY);
90       
91       format=AFMT_S16_LE;
92       if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format)==-1)
93       {
94          perror("SNDCTL_DSP_SETFMT");
95          close(audio_fd);
96          exit(1);
97       }
98       
99       stereo=0;
100       if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &stereo)==-1)
101       {
102          perror("SNDCTL_DSP_STEREO");
103          close(audio_fd);
104          exit(1);
105       }
106       if (stereo!=0)
107       {
108          fprintf (stderr, "Cannot set mono mode\n");
109          exit(1);
110       }
111
112       if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &rate)==-1)
113       {
114          perror("SNDCTL_DSP_SPEED");
115          close(audio_fd);
116          exit(1);
117       }
118       fout = fdopen(audio_fd, "w");
119 #elif defined WIN32 || defined _WIN32
120       if (Set_WIN_Params (INVALID_FILEDESC, rate, SAMPLE_SIZE, 1))
121       {
122          fprintf (stderr, "Can't access %s\n", "WAVE OUT");
123          exit(1);
124       }
125 #else
126       fprintf (stderr, "No soundcard support\n");
127       exit(1);
128 #endif
129    } else {
130       if (strcmp(outFile,"-")==0)
131       {
132 #if defined WIN32 || defined _WIN32
133          _setmode(_fileno(stdout), _O_BINARY);
134 #endif
135          fout=stdout;
136       }
137       else 
138       {
139 #if defined WIN32 || defined _WIN32
140          fout = fopen(outFile, "wb");
141 #else
142          fout = fopen(outFile, "w");
143 #endif
144          if (!fout)
145          {
146             perror(outFile);
147             exit(1);
148          }
149          if (strcmp(outFile+strlen(outFile)-4,".wav")==0 || strcmp(outFile+strlen(outFile)-4,".WAV")==0)
150             write_wav_header(fout, rate, 1, 0, 0);
151       }
152    }
153    return fout;
154 }
155
156 void usage()
157 {
158    fprintf (stderr, "Speex decoder version " VERSION " (compiled " __DATE__ ")\n");
159    fprintf (stderr, "\n");
160    fprintf (stderr, "usage: speexdec [options] input_file.spx\n");
161    fprintf (stderr, "       speexdec [options] input_file.spx output_file.wav\n");
162    fprintf (stderr, "\n");
163    fprintf (stderr, "input_file can be:\n");
164    fprintf (stderr, "  filename.spx          regular Speex file\n");
165    fprintf (stderr, "  -                     stdin\n");
166    fprintf (stderr, "\n");  
167    fprintf (stderr, "output_file can be:\n");
168    fprintf (stderr, "  filename.wav          wav file\n");
169    fprintf (stderr, "  filename.*            raw PCM file (any extension other that .wav)\n");
170    fprintf (stderr, "  -                     stdout\n");
171    fprintf (stderr, "  (nothing)             will be played to soundcard\n");
172    fprintf (stderr, "\n");  
173    fprintf (stderr, "options:\n");
174    fprintf (stderr, "  --enh                 Enable perceptual enhancement\n");
175    fprintf (stderr, "  --no-enh              Disable perceptual enhancement (default FOR NOW)\n");
176    fprintf (stderr, "  --force-nb            Force decoding in narrowband, even for wideband\n");
177    fprintf (stderr, "  --force-wb            Force decoding in wideband, even for narrowband\n");
178    fprintf (stderr, "  -V                    Verbose mode (show bit-rate)\n"); 
179    fprintf (stderr, "  --help       -h       This help\n");
180    fprintf (stderr, "  --version    -v       Version information\n");
181    fprintf (stderr, "  --pf                  Deprecated, use --pf instead\n");
182    fprintf (stderr, "  --no-pf               Deprecated, use --no-pf instead\n");
183 }
184
185 void version()
186 {
187    fprintf (stderr, "Speex decoder version " VERSION " (compiled " __DATE__ ")\n");
188 }
189
190 static void *process_header(ogg_packet *op, int enh_enabled, int *frame_size, int *rate, int *nframes, int forceMode)
191 {
192    void *st;
193    SpeexMode *mode;
194    SpeexHeader *header;
195    int modeID;
196    
197    header = speex_packet_to_header((char*)op->packet, op->bytes);
198    if (!header)
199    {
200       fprintf (stderr, "Cannot read header\n");
201       return NULL;
202    }
203    if (header->mode >= SPEEX_NB_MODES)
204    {
205       fprintf (stderr, "Mode number %d does not (any longer) exist in this version\n", 
206                header->mode);
207       return NULL;
208    }
209       
210    modeID = header->mode;
211    if (forceMode!=-1)
212       modeID = forceMode;
213    mode = speex_mode_list[modeID];
214    
215    if (mode->bitstream_version < header->mode_bitstream_version)
216    {
217       fprintf (stderr, "The file was encoded with a newer version of Speex. You need to upgrade in order to play it.\n");
218       return NULL;
219    }
220    if (mode->bitstream_version > header->mode_bitstream_version) 
221    {
222       fprintf (stderr, "The file was encoded with an older version of Speex. You would need to downgrade the version in order to play it.\n");
223       return NULL;
224    }
225    
226    st = speex_decoder_init(mode);
227    speex_decoder_ctl(st, SPEEX_SET_ENH, &enh_enabled);
228    speex_decoder_ctl(st, SPEEX_GET_FRAME_SIZE, frame_size);
229    
230    /* FIXME: need to adjust in case the forceMode option is set */
231    *rate = header->rate;
232    if (header->mode==1 && forceMode==0)
233       *rate/=2;
234    if (header->mode==0 && forceMode==1)
235       *rate*=2;
236    *nframes = header->frames_per_packet;
237    
238    fprintf (stderr, "Decoding %d Hz audio using %s mode", 
239             *rate, mode->modeName);
240
241    if (header->vbr)
242       fprintf (stderr, " (VBR)\n");
243    else
244       fprintf(stderr, "\n");
245    /*fprintf (stderr, "Decoding %d Hz audio at %d bps using %s mode\n", 
246     *rate, mode->bitrate, mode->modeName);*/
247
248    free(header);
249    return st;
250 }
251
252 int main(int argc, char **argv)
253 {
254    int c;
255    int option_index = 0;
256    char *inFile, *outFile;
257    FILE *fin, *fout=NULL;
258    short out[MAX_FRAME_SIZE];
259    float output[MAX_FRAME_SIZE];
260    int frame_size=0;
261    void *st=NULL;
262    SpeexBits bits;
263    int packet_count=0;
264    int stream_init = 0;
265    struct option long_options[] =
266    {
267       {"help", no_argument, NULL, 0},
268       {"version", no_argument, NULL, 0},
269       {"enh", no_argument, NULL, 0},
270       {"no-enh", no_argument, NULL, 0},
271       {"pf", no_argument, NULL, 0},
272       {"no-pf", no_argument, NULL, 0},
273       {"force-nb", no_argument, NULL, 0},
274       {"force-wb", no_argument, NULL, 0},
275       {0, 0, 0, 0}
276    };
277    ogg_sync_state oy;
278    ogg_page       og;
279    ogg_packet     op;
280    ogg_stream_state os;
281    int enh_enabled;
282    int nframes=2;
283    int print_bitrate=0;
284    int close_in=0;
285    int eos=0;
286    int forceMode=-1;
287    int audio_size=0;
288    enh_enabled = 0;
289
290    /*Process options*/
291    while(1)
292    {
293       c = getopt_long (argc, argv, "hvV",
294                        long_options, &option_index);
295       if (c==-1)
296          break;
297       
298       switch(c)
299       {
300       case 0:
301          if (strcmp(long_options[option_index].name,"help")==0)
302          {
303             usage();
304             exit(0);
305          } else if (strcmp(long_options[option_index].name,"version")==0)
306          {
307             version();
308             exit(0);
309          } else if (strcmp(long_options[option_index].name,"enh")==0)
310          {
311             enh_enabled=1;
312          } else if (strcmp(long_options[option_index].name,"no-enh")==0)
313          {
314             enh_enabled=0;
315          } else if (strcmp(long_options[option_index].name,"pf")==0)
316          {
317             fprintf (stderr, "--pf is deprecated, use --enh instead\n");
318             enh_enabled=1;
319          } else if (strcmp(long_options[option_index].name,"no-pf")==0)
320          {
321             fprintf (stderr, "--no-pf is deprecated, use --no-enh instead\n");
322             enh_enabled=0;
323          } else if (strcmp(long_options[option_index].name,"force-nb")==0)
324          {
325             forceMode=0;
326          } else if (strcmp(long_options[option_index].name,"force-wb")==0)
327          {
328             forceMode=1;
329          }
330          break;
331       case 'h':
332          usage();
333          exit(0);
334          break;
335       case 'v':
336          version();
337          exit(0);
338          break;
339       case 'V':
340          print_bitrate=1;
341          break;
342       case '?':
343          usage();
344          exit(1);
345          break;
346       }
347    }
348    if (argc-optind!=2 && argc-optind!=1)
349    {
350       usage();
351       exit(1);
352    }
353    inFile=argv[optind];
354
355    if (argc-optind==2)
356       outFile=argv[optind+1];
357    else
358       outFile = "";
359    /*Open input file*/
360    if (strcmp(inFile, "-")==0)
361    {
362 #if defined WIN32 || defined _WIN32
363       _setmode(_fileno(stdout), _O_BINARY);
364 #endif
365       fin=stdin;
366    }
367    else 
368    {
369 #if defined WIN32 || defined _WIN32
370       fin = fopen(inFile, "rb");
371 #else
372       fin = fopen(inFile, "r");
373 #endif
374       if (!fin)
375       {
376          perror(inFile);
377          exit(1);
378       }
379       close_in=1;
380    }
381
382
383    /*Init Ogg data struct*/
384    ogg_sync_init(&oy);
385    
386    speex_bits_init(&bits);
387    /*Main decoding loop*/
388    while (1)
389    {
390       char *data;
391       int i, j, nb_read;
392       /*Get the ogg buffer for writing*/
393       data = ogg_sync_buffer(&oy, 200);
394       /*Read bitstream from input file*/
395       nb_read = fread(data, sizeof(char), 200, fin);      
396       ogg_sync_wrote(&oy, nb_read);
397
398       /*Loop for all complete pages we got (most likely only one)*/
399       while (ogg_sync_pageout(&oy, &og)==1)
400       {
401          if (stream_init == 0) {
402             ogg_stream_init(&os, ogg_page_serialno(&og));
403             stream_init = 1;
404          }
405          /*Add page to the bitstream*/
406          ogg_stream_pagein(&os, &og);
407          /*Extract all available packets*/
408          while (!eos && ogg_stream_packetout(&os, &op)==1)
409          {
410             /*If first packet, process as Speex header*/
411             if (packet_count==0)
412             {
413                int rate;
414                st = process_header(&op, enh_enabled, &frame_size, &rate, &nframes, forceMode);
415                if (!nframes)
416                   nframes=1;
417                if (!st)
418                   exit(1);
419                fout = out_file_open(outFile, rate);
420
421             } else if (packet_count==1){
422                print_comments(op.packet, op.bytes);
423                /*
424                fprintf (stderr, "File comments: ");
425                fwrite(op.packet, 1, op.bytes, stderr);
426                fprintf (stderr, "\n");
427                */
428             } else {
429
430                /*End of stream condition*/
431                if (op.e_o_s)
432                   eos=1;
433
434                /*Copy Ogg packet to Speex bitstream*/
435                speex_bits_read_from(&bits, (char*)op.packet, op.bytes);
436                for (j=0;j<nframes;j++)
437                {
438                   /*Decode frame*/
439                   speex_decode(st, &bits, output);
440                
441                   if (print_bitrate) {
442                      int tmp;
443                      char ch=13;
444                      speex_decoder_ctl(st, SPEEX_GET_BITRATE, &tmp);
445                      fputc (ch, stderr);
446                      fprintf (stderr, "Bitrate is use: %d bps     ", tmp);
447                   }
448                   /*PCM saturation (just in case)*/
449                   for (i=0;i<frame_size;i++)
450                   {
451                      if (output[i]>32000)
452                         output[i]=32000;
453                      else if (output[i]<-32000)
454                         output[i]=-32000;
455                   }
456                   /*Convert to short and save to output file*/
457                   for (i=0;i<frame_size;i++)
458                      out[i]=(short)le_short(output[i]);
459 #if defined WIN32 || defined _WIN32
460                   if (strlen(outFile)==0)
461                       WIN_Play_Samples (out, sizeof(short) * frame_size);
462                   else
463 #endif
464                   fwrite(out, sizeof(short), frame_size, fout);
465                   audio_size+=sizeof(short)*frame_size;
466                }
467             }
468             packet_count++;
469          }
470       }
471       if (feof(fin))
472          break;
473
474    }
475
476    if (strcmp(outFile+strlen(outFile)-4,".wav")==0 || strcmp(inFile+strlen(inFile)-4,".WAV")==0)
477    {
478       if (fseek(fout,4,SEEK_SET)==0)
479       {
480          int tmp;
481          tmp = le_int(audio_size+36);
482          fwrite(&tmp,4,1,fout);
483          if (fseek(fout,32,SEEK_CUR)==0)
484          {
485             tmp = le_int(audio_size);
486             fwrite(&tmp,4,1,fout);
487          } else
488          {
489             fprintf (stderr, "First seek worked, second didn't\n");
490          }
491       } else {
492          fprintf (stderr, "Cannot seek on wave file, size will be incorrect\n");
493       }
494    }
495
496    if (st)
497       speex_decoder_destroy(st);
498    speex_bits_destroy(&bits);
499    ogg_sync_clear(&oy);
500    ogg_stream_clear(&os);
501
502    if (close_in)
503       fclose(fin);
504    fclose(fout);
505    return 1;
506 }