speexdec a bit more robust to corrupted files, misc doc fixes
[speexdsp.git] / src / speexdec.c
1 /* Copyright (C) 2002 Jean-Marc Valin 
2    File: speexdec.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
40 #include "speex.h"
41 #include "ogg/ogg.h"
42
43 #if defined WIN32 || defined _WIN32
44 #include <windows.h>
45 #include "getopt_win.h"
46 #include "wave_out.h"
47 /* We need the following two to set stdout to binary */
48 #include <io.h>
49 #include <fcntl.h>
50 #endif
51 #include <math.h>
52
53 #ifdef HAVE_SYS_SOUNDCARD_H
54 #include <sys/soundcard.h>
55 #include <sys/types.h>
56 #include <sys/stat.h>
57 #include <fcntl.h>
58 #include <sys/ioctl.h>
59 #endif
60
61 #include <string.h>
62 #include "wav_io.h"
63 #include "speex_header.h"
64 #include "speex_stereo.h"
65 #include "speex_callbacks.h"
66 #include "misc.h"
67
68 #define MAX_FRAME_SIZE 2000
69
70 #define readint(buf, base) (((buf[base+3]<<24)&0xff000000)| \
71                            ((buf[base+2]<<16)&0xff0000)| \
72                            ((buf[base+1]<<8)&0xff00)| \
73                             (buf[base]&0xff))
74
75 static void print_comments(char *comments, int length)
76 {
77    char *c=comments;
78    int len, i, nb_fields;
79    char *end;
80    
81    if (length<8)
82    {
83       fprintf (stderr, "Invalid/corrupted comments\n");
84       return;
85    }
86    end = c+length;
87    len=readint(c, 0);
88    c+=4;
89    if (c+len>=end)
90    {
91       fprintf (stderr, "Invalid/corrupted comments\n");
92       return;
93    }
94    fwrite(c, 1, len, stderr);
95    c+=len;
96    fprintf (stderr, "\n");
97    if (c+4>=end)
98    {
99       fprintf (stderr, "Invalid/corrupted comments\n");
100       return;
101    }
102    nb_fields=readint(c, 0);
103    c+=4;
104    for (i=0;i<nb_fields;i++)
105    {
106       if (c+4>=end)
107       {
108          fprintf (stderr, "Invalid/corrupted comments\n");
109          return;
110       }
111       len=readint(c, 0);
112       c+=4;
113       if (c+len>=end)
114       {
115          fprintf (stderr, "Invalid/corrupted comments\n");
116          return;
117       }
118       fwrite(c, 1, len, stderr);
119       c+=len;
120       fprintf (stderr, "\n");
121    }
122 }
123
124 FILE *out_file_open(char *outFile, int rate, int *channels)
125 {
126    FILE *fout=NULL;
127    /*Open output file*/
128    if (strlen(outFile)==0)
129    {
130 #if defined HAVE_SYS_SOUNDCARD_H
131       int audio_fd, format, stereo;
132       audio_fd=open("/dev/dsp", O_WRONLY);
133       if (audio_fd<0)
134       {
135          perror("Cannot open /dev/dsp");
136          exit(1);         
137       }
138
139       format=AFMT_S16_LE;
140       if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format)==-1)
141       {
142          perror("SNDCTL_DSP_SETFMT");
143          close(audio_fd);
144          exit(1);
145       }
146
147       stereo=0;
148       if (*channels==2)
149          stereo=1;
150       if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &stereo)==-1)
151       {
152          perror("SNDCTL_DSP_STEREO");
153          close(audio_fd);
154          exit(1);
155       }
156       if (stereo!=0)
157       {
158          if (*channels==1)
159             fprintf (stderr, "Cannot set mono mode, will decode in stereo\n");
160          *channels=2;
161       }
162
163       if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &rate)==-1)
164       {
165          perror("SNDCTL_DSP_SPEED");
166          close(audio_fd);
167          exit(1);
168       }
169       fout = fdopen(audio_fd, "w");
170 #elif defined WIN32 || defined _WIN32
171       {
172          unsigned int speex_channels = *channels;
173          if (Set_WIN_Params (INVALID_FILEDESC, rate, SAMPLE_SIZE, speex_channels))
174          {
175             fprintf (stderr, "Can't access %s\n", "WAVE OUT");
176             exit(1);
177          }
178       }
179 #else
180       fprintf (stderr, "No soundcard support\n");
181       exit(1);
182 #endif
183    } else {
184       if (strcmp(outFile,"-")==0)
185       {
186 #if defined WIN32 || defined _WIN32
187          _setmode(_fileno(stdout), _O_BINARY);
188 #endif
189          fout=stdout;
190       }
191       else 
192       {
193 #if defined WIN32 || defined _WIN32
194          fout = fopen(outFile, "wb");
195 #else
196          fout = fopen(outFile, "w");
197 #endif
198          if (!fout)
199          {
200             perror(outFile);
201             exit(1);
202          }
203          if (strcmp(outFile+strlen(outFile)-4,".wav")==0 || strcmp(outFile+strlen(outFile)-4,".WAV")==0)
204             write_wav_header(fout, rate, *channels, 0, 0);
205       }
206    }
207    return fout;
208 }
209
210 void usage()
211 {
212    printf ("Usage: speexdec [options] input_file.spx [output_file]\n");
213    printf ("\n");
214    printf ("Decodes a Speex file and produce a WAV file or raw file\n");
215    printf ("\n");
216    printf ("input_file can be:\n");
217    printf ("  filename.spx         regular Speex file\n");
218    printf ("  -                    stdin\n");
219    printf ("\n");  
220    printf ("output_file can be:\n");
221    printf ("  filename.wav         Wav file\n");
222    printf ("  filename.*           Raw PCM file (any extension other that .wav)\n");
223    printf ("  -                    stdout\n");
224    printf ("  (nothing)            Will be played to soundcard\n");
225    printf ("\n");  
226    printf ("Options:\n");
227    printf (" --enh                 Enable perceptual enhancement (default)\n");
228    printf (" --no-enh              Disable perceptual enhancement\n");
229    printf (" --force-nb            Force decoding in narrowband\n");
230    printf (" --force-wb            Force decoding in wideband\n");
231    printf (" --force-uwb           Force decoding in ultra-wideband\n");
232    printf (" --mono                Force decoding in mono\n");
233    printf (" --stereo              Force decoding in stereo\n");
234    printf (" --rate n              Force decoding at sampling rate n Hz\n");
235    printf (" --packet-loss n       Simulate n %% random packet loss\n");
236    printf (" -V                    Verbose mode (show bit-rate)\n"); 
237    printf (" -h, --help            This help\n");
238    printf (" -v, --version         Version information\n");
239    printf (" --pf                  Deprecated, use --enh instead\n");
240    printf (" --no-pf               Deprecated, use --no-enh instead\n");
241    printf ("\n");
242    printf ("More information is available from the Speex site: http://www.speex.org\n");
243    printf ("\n");
244    printf ("Please report bugs to the mailing list `speex-dev@xiph.org'.\n");
245 }
246
247 void version()
248 {
249    printf ("speexdec (Speex decoder) version " VERSION " (compiled " __DATE__ ")\n");
250    printf ("Copyright (C) 2002 Jean-Marc Valin\n");
251 }
252
253 void version_short()
254 {
255    printf ("speexdec version " VERSION "\n");
256    printf ("Copyright (C) 2002 Jean-Marc Valin\n");
257 }
258
259 static void *process_header(ogg_packet *op, int enh_enabled, int *frame_size, int *rate, int *nframes, int forceMode, int *channels, SpeexStereoState *stereo)
260 {
261    void *st;
262    SpeexMode *mode;
263    SpeexHeader *header;
264    int modeID;
265    SpeexCallback callback;
266       
267    header = speex_packet_to_header((char*)op->packet, op->bytes);
268    if (!header)
269    {
270       fprintf (stderr, "Cannot read header\n");
271       return NULL;
272    }
273    if (header->mode >= SPEEX_NB_MODES)
274    {
275       fprintf (stderr, "Mode number %d does not (any longer) exist in this version\n", 
276                header->mode);
277       return NULL;
278    }
279       
280    modeID = header->mode;
281    if (forceMode!=-1)
282       modeID = forceMode;
283    mode = speex_mode_list[modeID];
284    
285    if (mode->bitstream_version < header->mode_bitstream_version)
286    {
287       fprintf (stderr, "The file was encoded with a newer version of Speex. You need to upgrade in order to play it.\n");
288       return NULL;
289    }
290    if (mode->bitstream_version > header->mode_bitstream_version) 
291    {
292       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");
293       return NULL;
294    }
295    
296    st = speex_decoder_init(mode);
297    speex_decoder_ctl(st, SPEEX_SET_ENH, &enh_enabled);
298    speex_decoder_ctl(st, SPEEX_GET_FRAME_SIZE, frame_size);
299
300    callback.callback_id = SPEEX_INBAND_STEREO;
301    callback.func = speex_std_stereo_request_handler;
302    callback.data = stereo;
303    speex_decoder_ctl(st, SPEEX_SET_HANDLER, &callback);
304    
305    if (!*rate)
306       *rate = header->rate;
307    /* Adjust rate if --force-* options are used */
308    if (forceMode!=-1)
309    {
310       if (header->mode < forceMode)
311          *rate <<= (forceMode - header->mode);
312       if (header->mode > forceMode)
313          *rate >>= (header->mode - forceMode);
314    }
315
316    speex_decoder_ctl(st, SPEEX_SET_SAMPLING_RATE, rate);
317
318    *nframes = header->frames_per_packet;
319
320    if (*channels==-1)
321       *channels = header->nb_channels;
322    
323    fprintf (stderr, "Decoding %d Hz audio using %s mode", 
324             *rate, mode->modeName);
325
326    if (header->vbr)
327       fprintf (stderr, " (VBR)\n");
328    else
329       fprintf(stderr, "\n");
330    /*fprintf (stderr, "Decoding %d Hz audio at %d bps using %s mode\n", 
331     *rate, mode->bitrate, mode->modeName);*/
332
333    free(header);
334    return st;
335 }
336
337 int main(int argc, char **argv)
338 {
339    int c;
340    int option_index = 0;
341    char *inFile, *outFile;
342    FILE *fin, *fout=NULL;
343    short out[MAX_FRAME_SIZE];
344    float output[MAX_FRAME_SIZE];
345    int frame_size=0;
346    void *st=NULL;
347    SpeexBits bits;
348    int packet_count=0;
349    int stream_init = 0;
350    struct option long_options[] =
351    {
352       {"help", no_argument, NULL, 0},
353       {"version", no_argument, NULL, 0},
354       {"version-short", no_argument, NULL, 0},
355       {"enh", no_argument, NULL, 0},
356       {"no-enh", no_argument, NULL, 0},
357       {"pf", no_argument, NULL, 0},
358       {"no-pf", no_argument, NULL, 0},
359       {"force-nb", no_argument, NULL, 0},
360       {"force-wb", no_argument, NULL, 0},
361       {"force-uwb", no_argument, NULL, 0},
362       {"rate", required_argument, NULL, 0},
363       {"mono", no_argument, NULL, 0},
364       {"stereo", no_argument, NULL, 0},
365       {"packet-loss", required_argument, NULL, 0},
366       {0, 0, 0, 0}
367    };
368    ogg_sync_state oy;
369    ogg_page       og;
370    ogg_packet     op;
371    ogg_stream_state os;
372    int enh_enabled;
373    int nframes=2;
374    int print_bitrate=0;
375    int close_in=0;
376    int eos=0;
377    int forceMode=-1;
378    int audio_size=0;
379    float loss_percent=-1;
380    SpeexStereoState stereo = SPEEX_STEREO_STATE_INIT;
381    int channels=-1;
382    int rate=0;
383
384    enh_enabled = 1;
385
386    /*Process options*/
387    while(1)
388    {
389       c = getopt_long (argc, argv, "hvV",
390                        long_options, &option_index);
391       if (c==-1)
392          break;
393       
394       switch(c)
395       {
396       case 0:
397          if (strcmp(long_options[option_index].name,"help")==0)
398          {
399             usage();
400             exit(0);
401          } else if (strcmp(long_options[option_index].name,"version")==0)
402          {
403             version();
404             exit(0);
405          } else if (strcmp(long_options[option_index].name,"version-short")==0)
406          {
407             version_short();
408             exit(0);
409          } else if (strcmp(long_options[option_index].name,"enh")==0)
410          {
411             enh_enabled=1;
412          } else if (strcmp(long_options[option_index].name,"no-enh")==0)
413          {
414             enh_enabled=0;
415          } else if (strcmp(long_options[option_index].name,"pf")==0)
416          {
417             fprintf (stderr, "--pf is deprecated, use --enh instead\n");
418             enh_enabled=1;
419          } else if (strcmp(long_options[option_index].name,"no-pf")==0)
420          {
421             fprintf (stderr, "--no-pf is deprecated, use --no-enh instead\n");
422             enh_enabled=0;
423          } else if (strcmp(long_options[option_index].name,"force-nb")==0)
424          {
425             forceMode=0;
426          } else if (strcmp(long_options[option_index].name,"force-wb")==0)
427          {
428             forceMode=1;
429          } else if (strcmp(long_options[option_index].name,"force-uwb")==0)
430          {
431             forceMode=2;
432          } else if (strcmp(long_options[option_index].name,"mono")==0)
433          {
434             channels=1;
435          } else if (strcmp(long_options[option_index].name,"stereo")==0)
436          {
437             channels=2;
438          } else if (strcmp(long_options[option_index].name,"rate")==0)
439          {
440             rate=atoi (optarg);
441          } else if (strcmp(long_options[option_index].name,"packet-loss")==0)
442          {
443             loss_percent = atof(optarg);
444          }
445          break;
446       case 'h':
447          usage();
448          exit(0);
449          break;
450       case 'v':
451          version();
452          exit(0);
453          break;
454       case 'V':
455          print_bitrate=1;
456          break;
457       case '?':
458          usage();
459          exit(1);
460          break;
461       }
462    }
463    if (argc-optind!=2 && argc-optind!=1)
464    {
465       usage();
466       exit(1);
467    }
468    inFile=argv[optind];
469
470    if (argc-optind==2)
471       outFile=argv[optind+1];
472    else
473       outFile = "";
474    /*Open input file*/
475    if (strcmp(inFile, "-")==0)
476    {
477 #if defined WIN32 || defined _WIN32
478       _setmode(_fileno(stdin), _O_BINARY);
479 #endif
480       fin=stdin;
481    }
482    else 
483    {
484 #if defined WIN32 || defined _WIN32
485       fin = fopen(inFile, "rb");
486 #else
487       fin = fopen(inFile, "r");
488 #endif
489       if (!fin)
490       {
491          perror(inFile);
492          exit(1);
493       }
494       close_in=1;
495    }
496
497
498    /*Init Ogg data struct*/
499    ogg_sync_init(&oy);
500    
501    speex_bits_init(&bits);
502    /*Main decoding loop*/
503    while (1)
504    {
505       char *data;
506       int i, j, nb_read;
507       /*Get the ogg buffer for writing*/
508       data = ogg_sync_buffer(&oy, 200);
509       /*Read bitstream from input file*/
510       nb_read = fread(data, sizeof(char), 200, fin);      
511       ogg_sync_wrote(&oy, nb_read);
512
513       /*Loop for all complete pages we got (most likely only one)*/
514       while (ogg_sync_pageout(&oy, &og)==1)
515       {
516          if (stream_init == 0) {
517             ogg_stream_init(&os, ogg_page_serialno(&og));
518             stream_init = 1;
519          }
520          /*Add page to the bitstream*/
521          ogg_stream_pagein(&os, &og);
522          /*Extract all available packets*/
523          while (!eos && ogg_stream_packetout(&os, &op)==1)
524          {
525             /*If first packet, process as Speex header*/
526             if (packet_count==0)
527             {
528                st = process_header(&op, enh_enabled, &frame_size, &rate, &nframes, forceMode, &channels, &stereo);
529                if (!nframes)
530                   nframes=1;
531                if (!st)
532                   exit(1);
533                fout = out_file_open(outFile, rate, &channels);
534
535             } else if (packet_count==1){
536                print_comments((char*)op.packet, op.bytes);
537                /*
538                fprintf (stderr, "File comments: ");
539                fwrite(op.packet, 1, op.bytes, stderr);
540                fprintf (stderr, "\n");
541                */
542             } else {
543                
544                int lost=0;
545                if (loss_percent>0 && 100*((float)rand())/RAND_MAX<loss_percent)
546                   lost=1;
547
548                /*End of stream condition*/
549                if (op.e_o_s)
550                   eos=1;
551
552                /*Copy Ogg packet to Speex bitstream*/
553                speex_bits_read_from(&bits, (char*)op.packet, op.bytes);
554                for (j=0;j<nframes;j++)
555                {
556                   /*Decode frame*/
557                   if (!lost)
558                      speex_decode(st, &bits, output);
559                   else
560                      speex_decode(st, NULL, output);
561
562                   if (channels==2)
563                      speex_decode_stereo(output, frame_size, &stereo);
564
565                   if (print_bitrate) {
566                      int tmp;
567                      char ch=13;
568                      speex_decoder_ctl(st, SPEEX_GET_BITRATE, &tmp);
569                      fputc (ch, stderr);
570                      fprintf (stderr, "Bitrate is use: %d bps     ", tmp);
571                   }
572                   /*PCM saturation (just in case)*/
573                   for (i=0;i<frame_size*channels;i++)
574                   {
575                      if (output[i]>32000.0)
576                         output[i]=32000.0;
577                      else if (output[i]<-32000.0)
578                         output[i]=-32000.0;
579                   }
580                   /*Convert to short and save to output file*/
581                   for (i=0;i<frame_size*channels;i++)
582                      out[i]=(short)le_short((short)floor(.5+output[i]));
583 #if defined WIN32 || defined _WIN32
584                   if (strlen(outFile)==0)
585                       WIN_Play_Samples (out, sizeof(short) * frame_size*channels);
586                   else
587 #endif
588                   fwrite(out, sizeof(short), frame_size*channels, fout);
589                   
590                   audio_size+=sizeof(short)*frame_size*channels;
591                }
592             }
593             packet_count++;
594          }
595       }
596       if (feof(fin))
597          break;
598
599    }
600
601    if (strcmp(outFile+strlen(outFile)-4,".wav")==0 || strcmp(inFile+strlen(inFile)-4,".WAV")==0)
602    {
603       if (fseek(fout,4,SEEK_SET)==0)
604       {
605          int tmp;
606          tmp = le_int(audio_size+36);
607          fwrite(&tmp,4,1,fout);
608          if (fseek(fout,32,SEEK_CUR)==0)
609          {
610             tmp = le_int(audio_size);
611             fwrite(&tmp,4,1,fout);
612          } else
613          {
614             fprintf (stderr, "First seek worked, second didn't\n");
615          }
616       } else {
617          fprintf (stderr, "Cannot seek on wave file, size will be incorrect\n");
618       }
619    }
620
621    if (st)
622       speex_decoder_destroy(st);
623    else 
624    {
625       fprintf (stderr, "This doesn't look like a Speex file\n");
626    }
627    speex_bits_destroy(&bits);
628    ogg_sync_clear(&oy);
629    ogg_stream_clear(&os);
630
631 #if defined WIN32 || defined _WIN32
632    if (fout && strlen(outFile)==0)
633       WIN_Audio_close ();
634 #endif
635
636    if (close_in)
637       fclose(fin);
638    if (fout != NULL)
639       fclose(fout);   
640
641    return 0;
642 }