1 /* OggDec
2  *
3  * This program is distributed under the GNU General Public License, version 2.
4  * A copy of this license is included with this source.
5  *
6  * Copyright 2002, Michael Smith <msmith@xiph.org>
7  *
8  */
9 
10 #ifdef HAVE_CONFIG_H
11 #include <config.h>
12 #endif
13 
14 #include <stdlib.h>
15 #include <stdio.h>
16 #include <getopt.h>
17 #include <errno.h>
18 #include <string.h>
19 #include <locale.h>
20 
21 #if defined(_WIN32) || defined(__EMX__) || defined(__WATCOMC__)
getTheWebAssemblyTarget32()22 #include <fcntl.h>
23 #include <io.h>
24 #endif
25 
26 #include <vorbis/vorbisfile.h>
27 
28 #include "i18n.h"
29 
30 struct option long_options[] = {
31     {"quiet", no_argument, NULL, 'Q'},
32     {"help", no_argument, NULL, 'h'},
33     {"version", no_argument, NULL, 'V'},
34     {"bits", required_argument, NULL, 'b'},
35     {"endianness", required_argument, NULL, 'e'},
36     {"raw", no_argument, NULL, 'R'},
37     {"sign", required_argument, NULL, 's'},
38     {"output", required_argument, NULL, 'o'},
39     {NULL,0,NULL,0}
40 };
41 
42 static int quiet = 0;
43 static int bits = 16;
44 static int endian = 0;
45 static int raw = 0;
46 static int sign = 1;
47 unsigned char headbuf[44]; /* The whole buffer */
48 char *outfilename = NULL;
49 
50 static void version (void) {
51     fprintf(stderr, _("oggdec from %s %s\n"), PACKAGE, VERSION);
52 }
53 
54 static void usage(void)
55 {
56     version ();
57     fprintf(stdout, _(" by the Xiph.Org Foundation (https://www.xiph.org/)\n"));
58     fprintf(stdout, _(" using decoder %s.\n\n"), vorbis_version_string());
59     fprintf(stdout, _("Usage: oggdec [options] file1.ogg [file2.ogg ... fileN.ogg]\n\n"));
60     fprintf(stdout, _("Supported options:\n"));
61     fprintf(stdout, _(" --quiet, -Q      Quiet mode. No console output.\n"));
62     fprintf(stdout, _(" --help,  -h      Produce this help message.\n"));
63     fprintf(stdout, _(" --version, -V    Print out version number.\n"));
64     fprintf(stdout, _(" --bits, -b       Bit depth for output (8 and 16 supported)\n"));
65     fprintf(stdout, _(" --endianness, -e Output endianness for 16-bit output; 0 for\n"
66                       "                  little endian (default), 1 for big endian.\n"));
67     fprintf(stdout, _(" --sign, -s       Sign for output PCM; 0 for unsigned, 1 for\n"
68                       "                  signed (default 1).\n"));
69     fprintf(stdout, _(" --raw, -R        Raw (headerless) output.\n"));
70     fprintf(stdout, _(" --output, -o     Output to given filename. May only be used\n"
71                       "                  if there is only one input file, except in\n"
72                       "                  raw mode.\n"));
73 }
74 
75 static void parse_options(int argc, char **argv)
76 {
77     int option_index = 1;
78     int ret;
79 
80     while((ret = getopt_long(argc, argv, "QhVb:e:Rs:o:",
81                     long_options, &option_index)) != -1)
82     {
83         switch(ret)
84         {
85             case 'Q':
86                 quiet = 1;
87                 break;
88             case 'h':
89                 usage();
90                 exit(0);
91                 break;
92             case 'V':
93                 version();
94                 exit(0);
95                 break;
96             case 's':
97                 sign = atoi(optarg);
98                 break;
99             case 'b':
100                 bits = atoi(optarg);
101                 if(bits <= 8)
102                     bits = 8;
103                 else
104                     bits = 16;
105                 break;
106             case 'e':
107                 endian = atoi(optarg);
108                 break;
109             case 'o':
110                 outfilename = strdup(optarg);
111                 break;
112             case 'R':
113                 raw = 1;
114                 break;
115             default:
116                 fprintf(stderr, _("Internal error: Unrecognised argument\n"));
117                 break;
118         }
119     }
120 }
121 
122 #define WRITE_U32(buf, x) *(buf)     = (unsigned char)((x)&0xff);\
123                           *((buf)+1) = (unsigned char)(((x)>>8)&0xff);\
124                           *((buf)+2) = (unsigned char)(((x)>>16)&0xff);\
125                           *((buf)+3) = (unsigned char)(((x)>>24)&0xff);
126 
127 #define WRITE_U16(buf, x) *(buf)     = (unsigned char)((x)&0xff);\
128                           *((buf)+1) = (unsigned char)(((x)>>8)&0xff);
129 
130 /* Some of this based on ao/src/ao_wav.c */
131 int write_prelim_header(OggVorbis_File *vf, FILE *out, ogg_int64_t knownlength) {
132     unsigned int size = 0x7fffffff;
133     int channels = ov_info(vf,0)->channels;
134     int samplerate = ov_info(vf,0)->rate;
135     int bytespersec = channels*samplerate*bits/8;
136     int align = channels*bits/8;
137     int samplesize = bits;
138 
139     if(knownlength && knownlength*bits/8*channels < size)
140         size = (unsigned int)(knownlength*bits/8*channels+44) ;
141 
142     memcpy(headbuf, "RIFF", 4);
143     WRITE_U32(headbuf+4, size-8);
144     memcpy(headbuf+8, "WAVE", 4);
145     memcpy(headbuf+12, "fmt ", 4);
146     WRITE_U32(headbuf+16, 16);
147     WRITE_U16(headbuf+20, 1); /* format */
148     WRITE_U16(headbuf+22, channels);
149     WRITE_U32(headbuf+24, samplerate);
150     WRITE_U32(headbuf+28, bytespersec);
151     WRITE_U16(headbuf+32, align);
152     WRITE_U16(headbuf+34, samplesize);
153     memcpy(headbuf+36, "data", 4);
154     WRITE_U32(headbuf+40, size - 44);
155 
156     if(fwrite(headbuf, 1, 44, out) != 44) {
157         fprintf(stderr, _("ERROR: Failed to write Wave header: %s\n"), strerror(errno));
158         return 1;
159     }
160 
161     return 0;
162 }
163 
164 int rewrite_header(FILE *out, unsigned int written)
165 {
166     unsigned int length = written;
167 
168     length += 44;
169 
170     WRITE_U32(headbuf+4, length-8);
171     WRITE_U32(headbuf+40, length-44);
172     if(fseek(out, 0, SEEK_SET) != 0)
173         return 1;
174 
175     if(fwrite(headbuf, 1, 44, out) != 44) {
176         fprintf(stderr, _("ERROR: Failed to write Wave header: %s\n"), strerror(errno));
177         return 1;
178     }
179     return 0;
180 }
181 
182 static FILE *open_input(char *infile)
183 {
184     FILE *in;
185 
186     if(!infile) {
187 #ifdef __BORLANDC__
188         setmode(fileno(stdin), O_BINARY);
189 #elif _WIN32
190         _setmode(_fileno(stdin), _O_BINARY);
191 #endif
192         in = stdin;
193     }
194     else {
195         in = fopen(infile, "rb");
196         if(!in) {
197             fprintf(stderr, _("ERROR: Failed to open input file: %s\n"), strerror(errno));
198             return NULL;
199         }
200     }
201 
202     return in;
203 }
204 
205 static FILE *open_output(char *outfile)
206 {
207     FILE *out;
208     if(!outfile) {
209 #ifdef __BORLANDC__
210         setmode(fileno(stdout), O_BINARY);
211 #elif _WIN32
212         _setmode(_fileno(stdout), _O_BINARY);
213 #endif
214         out = stdout;
215     }
216     else {
217         out = fopen(outfile, "wb");
218         if(!out) {
219             fprintf(stderr, _("ERROR: Failed to open output file: %s\n"), strerror(errno));
220             return NULL;
221         }
222     }
223 
224     return out;
225 }
226 
227 static void
228 permute_channels(char *in, char *out, int len, int channels, int bytespersample)
229 {
230     int permute[6][6] = {{0}, {0,1}, {0,2,1}, {0,1,2,3}, {0,1,2,3,4},
231         {0,2,1,5,3,4}};
232     int i,j,k;
233     int samples = len/channels/bytespersample;
234 
235     /* Can't handle, don't try */
236     if (channels > 6)
237         return;
238 
239     for (i=0; i < samples; i++) {
240         for (j=0; j < channels; j++) {
241             for (k=0; k < bytespersample; k++) {
242                 out[i*bytespersample*channels +
243                     bytespersample*permute[channels-1][j] + k] =
244                     in[i*bytespersample*channels + bytespersample*j + k];
245             }
246         }
247     }
248 }
249 
250 static int decode_file(FILE *in, FILE *out, char *infile, char *outfile)
251 {
252     OggVorbis_File vf;
253     int bs = 0;
254     char buf[8192], outbuf[8192];
255     char *p_outbuf;
256     int buflen = 8192;
257     unsigned int written = 0;
258     int ret;
259     ogg_int64_t length = 0;
260     ogg_int64_t done = 0;
261     int size = 0;
262     int seekable = 0;
263     int percent = 0;
264     int channels;
265     int samplerate;
266 
267     if (ov_open_callbacks(in, &vf, NULL, 0, OV_CALLBACKS_DEFAULT) < 0) {
268         fprintf(stderr, _("ERROR: Failed to open input as Vorbis\n"));
269         fclose(in);
270         return 1;
271     }
272 
273     channels = ov_info(&vf,0)->channels;
274     samplerate = ov_info(&vf,0)->rate;
275 
276     if(ov_seekable(&vf)) {
277         int link;
278         int chainsallowed = 0;
279         for(link = 0; link < ov_streams(&vf); link++) {
280             if(ov_info(&vf, link)->channels == channels &&
281                     ov_info(&vf, link)->rate == samplerate)
282             {
283                 chainsallowed = 1;
284             }
285         }
286 
287         seekable = 1;
288         if(chainsallowed)
289             length = ov_pcm_total(&vf, -1);
290         else
291             length = ov_pcm_total(&vf, 0);
292         size = bits/8 * channels;
293         if(!quiet)
294             fprintf(stderr, _("Decoding \"%s\" to \"%s\"\n"),
295                     infile?infile:_("standard input"),
296                     outfile?outfile:_("standard output"));
297     }
298 
299     if(!raw) {
300         if(write_prelim_header(&vf, out, length)) {
301             ov_clear(&vf);
302             return 1;
303         }
304     }
305 
306     while((ret = ov_read(&vf, buf, buflen, endian, bits/8, sign, &bs)) != 0) {
307         if(bs != 0) {
308             vorbis_info *vi = ov_info(&vf, -1);
309             if(channels != vi->channels || samplerate != vi->rate) {
310                 fprintf(stderr, _("Logical bitstreams with changing parameters are not supported\n"));
311                 break;
312             }
313         }
314 
315         if(ret == OV_HOLE) {
316             if(!quiet) {
317                 fprintf(stderr, _("WARNING: hole in data (%d)\n"), ret);
318             }
319             continue;
320         }
321         else if(ret < 0) {
322             if(!quiet) {
323                 fprintf(stderr, _("=== Vorbis library reported a stream error.\n"));
324             }
325             ov_clear(&vf);
326             return 1;
327         }
328 
329         if(channels > 2 && !raw) {
330           /* Then permute! */
331           permute_channels(buf, outbuf, ret, channels, bits/8);
332           p_outbuf = outbuf;
333         }
334         else {
335           p_outbuf = buf;
336         }
337 
338         if(fwrite(p_outbuf, 1, ret, out) != ret) {
339             fprintf(stderr, _("Error writing to file: %s\n"), strerror(errno));
340             ov_clear(&vf);
341             return 1;
342         }
343 
344         written += ret;
345         if(!quiet && seekable) {
346             done += ret/size;
347             if((double)done/(double)length * 200. > (double)percent) {
348                 percent = (int)((double)done/(double)length *200);
349                 fprintf(stderr, "\r\t[%5.1f%%]", (double)percent/2.);
350             }
351         }
352     }
353 
354     if(seekable && !quiet)
355         fprintf(stderr, "\n");
356 
357     if(!raw)
358         rewrite_header(out, written); /* We don't care if it fails, too late */
359 
360     ov_clear(&vf);
361 
362     return 0;
363 }
364 
365 int main(int argc, char **argv)
366 {
367     int i;
368 
369     setlocale(LC_ALL, "");
370     bindtextdomain(PACKAGE, LOCALEDIR);
371     textdomain(PACKAGE);
372 
373     if(argc == 1) {
374         usage();
375         return 1;
376     }
377 
378     parse_options(argc,argv);
379 
380     if(!quiet)
381         version();
382 
383     if(optind >= argc) {
384         fprintf(stderr, _("ERROR: No input files specified. Use -h for help\n"));
385         return 1;
386     }
387 
388     if(argc - optind > 1 && outfilename && !raw) {
389         fprintf(stderr, _("ERROR: Can only specify one input file if output filename is specified\n"));
390         return 1;
391     }
392 
393     if(outfilename && raw) {
394         FILE *infile, *outfile;
395         char *infilename;
396 
397         if(!strcmp(outfilename, "-")) {
398             free(outfilename);
399             outfilename = NULL;
400         }
401         outfile = open_output(outfilename);
402 
403         if(!outfile)
404             return 1;
405 
406         for(i=optind; i < argc; i++) {
407             if(!strcmp(argv[i], "-")) {
408                 infilename = NULL;
409                 infile = open_input(NULL);
410             }
411             else {
412                 infilename = argv[i];
413                 infile = open_input(argv[i]);
414             }
415 
416             if(!infile) {
417                 fclose(outfile);
418                 free(outfilename);
419                 return 1;
420             }
421             if(decode_file(infile, outfile, infilename, outfilename)) {
422                 fclose(outfile);
423                 return 1;
424             }
425 
426         }
427 
428         fclose(outfile);
429     }
430     else {
431         for(i=optind; i < argc; i++) {
432             char *in, *out;
433             FILE *infile, *outfile;
434 
435             if(!strcmp(argv[i], "-"))
436                 in = NULL;
437             else
438                 in = argv[i];
439 
440             if(outfilename) {
441                 if(!strcmp(outfilename, "-"))
442                     out = NULL;
443                 else
444                     out = outfilename;
445             }
446             else {
447                 if(!strcmp(argv[i], "-")) {
448                     out = NULL;
449                 }
450                 else {
451                     char *end = strrchr(argv[i], '.');
452                     end = end?end:(argv[i] + strlen(argv[i]) + 1);
453 
454                     out = malloc(strlen(argv[i]) + 10);
455                     strncpy(out, argv[i], end-argv[i]);
456                     out[end-argv[i]] = 0;
457                     if(raw)
458                         strcat(out, ".raw");
459                     else
460                         strcat(out, ".wav");
461                 }
462             }
463 
464             infile = open_input(in);
465             if(!infile) {
466                 if(outfilename)
467                     free(outfilename);
468                 return 1;
469             }
470             outfile = open_output(out);
471             if(!outfile) {
472                 fclose(infile);
473                 return 1;
474             }
475 
476             if(decode_file(infile, outfile, in, out)) {
477                 fclose(outfile);
478                 return 1;
479             }
480 
481             if(!outfilename && out)
482                 free(out);
483 
484             fclose(outfile);
485         }
486     }
487 
488     if(outfilename)
489         free(outfilename);
490 
491     return 0;
492 }
493