Commit | Line | Data |
---|---|---|
f8f8039f RK |
1 | /* |
2 | * This file is part of DisOrder | |
4778e044 | 3 | * Copyright (C) 2007-2009 Richard Kettlewell |
f8f8039f | 4 | * |
e7eb3a27 | 5 | * This program is free software: you can redistribute it and/or modify |
f8f8039f | 6 | * it under the terms of the GNU General Public License as published by |
e7eb3a27 | 7 | * the Free Software Foundation, either version 3 of the License, or |
f8f8039f RK |
8 | * (at your option) any later version. |
9 | * | |
e7eb3a27 RK |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
f8f8039f | 15 | * You should have received a copy of the GNU General Public License |
e7eb3a27 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
f8f8039f RK |
17 | */ |
18 | /** @file server/decode.c | |
19 | * @brief General-purpose decoder for use by speaker process | |
20 | */ | |
21 | ||
05b75f8d | 22 | #include "disorder-server.h" |
11680e19 | 23 | #include "hreader.h" |
f8f8039f | 24 | |
f8f8039f | 25 | #include <mad.h> |
572c2899 | 26 | #include <vorbis/vorbisfile.h> |
762806f1 | 27 | |
7b203d74 | 28 | #include <FLAC/stream_decoder.h> |
f8f8039f | 29 | |
ce6c36be | 30 | #include "wav.h" |
39492555 | 31 | #include "speaker-protocol.h" |
f8f8039f | 32 | |
e7eb3a27 | 33 | |
f8f8039f RK |
34 | /** @brief Encoding lookup table type */ |
35 | struct decoder { | |
36 | /** @brief Glob pattern matching file */ | |
37 | const char *pattern; | |
38 | /** @brief Decoder function */ | |
39 | void (*decode)(void); | |
40 | }; | |
41 | ||
11680e19 | 42 | static struct hreader input[1]; |
f8f8039f RK |
43 | |
44 | /** @brief Output file */ | |
45 | static FILE *outputfp; | |
46 | ||
47 | /** @brief Filename */ | |
48 | static const char *path; | |
49 | ||
50 | /** @brief Input buffer */ | |
75db8354 | 51 | static char input_buffer[1048576]; |
f8f8039f | 52 | |
87b5259b | 53 | /** @brief Number of bytes read into buffer */ |
54 | static int input_count; | |
f8f8039f | 55 | |
75db8354 | 56 | /** @brief Write an 8-bit word */ |
57 | static inline void output_8(int n) { | |
58 | if(putc(n, outputfp) < 0) | |
2e9ba080 | 59 | disorder_fatal(errno, "decoding %s: output error", path); |
75db8354 | 60 | } |
61 | ||
f8f8039f RK |
62 | /** @brief Write a 16-bit word in bigendian format */ |
63 | static inline void output_16(uint16_t n) { | |
64 | if(putc(n >> 8, outputfp) < 0 | |
75db8354 | 65 | || putc(n, outputfp) < 0) |
2e9ba080 | 66 | disorder_fatal(errno, "decoding %s: output error", path); |
75db8354 | 67 | } |
68 | ||
69 | /** @brief Write a 24-bit word in bigendian format */ | |
70 | static inline void output_24(uint32_t n) { | |
71 | if(putc(n >> 16, outputfp) < 0 | |
72 | || putc(n >> 8, outputfp) < 0 | |
73 | || putc(n, outputfp) < 0) | |
2e9ba080 | 74 | disorder_fatal(errno, "decoding %s: output error", path); |
f8f8039f RK |
75 | } |
76 | ||
75db8354 | 77 | /** @brief Write a 32-bit word in bigendian format */ |
78 | static inline void output_32(uint32_t n) { | |
79 | if(putc(n >> 24, outputfp) < 0 | |
80 | || putc(n >> 16, outputfp) < 0 | |
81 | || putc(n >> 8, outputfp) < 0 | |
82 | || putc(n, outputfp) < 0) | |
2e9ba080 | 83 | disorder_fatal(errno, "decoding %s: output error", path); |
75db8354 | 84 | } |
85 | ||
86 | /** @brief Write a block header | |
87 | * @param rate Sample rate in Hz | |
88 | * @param channels Channel count (currently only 1 or 2 supported) | |
89 | * @param bits Bits per sample (must be a multiple of 8, no more than 64) | |
90 | * @param nbytes Total number of data bytes | |
91 | * @param endian @ref ENDIAN_BIG or @ref ENDIAN_LITTLE | |
92 | * | |
93 | * Checks that the sample format is a supported one (so other calls do not have | |
2e9ba080 | 94 | * to) and calls disorder_fatal() on error. |
f8f8039f RK |
95 | */ |
96 | static void output_header(int rate, | |
97 | int channels, | |
39492555 | 98 | int bits, |
ce6c36be | 99 | int nbytes, |
100 | int endian) { | |
39492555 | 101 | struct stream_header header; |
102 | ||
75db8354 | 103 | if(bits <= 0 || bits % 8 || bits > 64) |
2e9ba080 RK |
104 | disorder_fatal(0, "decoding %s: unsupported sample size %d bits", |
105 | path, bits); | |
75db8354 | 106 | if(channels <= 0 || channels > 2) |
2e9ba080 RK |
107 | disorder_fatal(0, "decoding %s: unsupported channel count %d", |
108 | path, channels); | |
75db8354 | 109 | if(rate <= 0) |
2e9ba080 | 110 | disorder_fatal(0, "decoding %s: nonsensical sample rate %dHz", path, rate); |
39492555 | 111 | header.rate = rate; |
112 | header.bits = bits; | |
113 | header.channels = channels; | |
ce6c36be | 114 | header.endian = endian; |
39492555 | 115 | header.nbytes = nbytes; |
116 | if(fwrite(&header, sizeof header, 1, outputfp) < 1) | |
2e9ba080 | 117 | disorder_fatal(errno, "decoding %s: writing format header", path); |
f8f8039f RK |
118 | } |
119 | ||
120 | /** @brief Dithering state | |
121 | * Filched from mpg321, which credits it to Robert Leslie */ | |
122 | struct audio_dither { | |
123 | mad_fixed_t error[3]; | |
124 | mad_fixed_t random; | |
125 | }; | |
126 | ||
127 | /** @brief 32-bit PRNG | |
128 | * Filched from mpg321, which credits it to Robert Leslie */ | |
129 | static inline unsigned long prng(unsigned long state) | |
130 | { | |
131 | return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; | |
132 | } | |
133 | ||
134 | /** @brief Generic linear sample quantize and dither routine | |
135 | * Filched from mpg321, which credits it to Robert Leslie */ | |
f8f8039f RK |
136 | static long audio_linear_dither(mad_fixed_t sample, |
137 | struct audio_dither *dither) { | |
138 | unsigned int scalebits; | |
139 | mad_fixed_t output, mask, rnd; | |
87b5259b | 140 | const int bits = 16; |
f8f8039f RK |
141 | |
142 | enum { | |
143 | MIN = -MAD_F_ONE, | |
144 | MAX = MAD_F_ONE - 1 | |
145 | }; | |
146 | ||
147 | /* noise shape */ | |
148 | sample += dither->error[0] - dither->error[1] + dither->error[2]; | |
149 | ||
150 | dither->error[2] = dither->error[1]; | |
151 | dither->error[1] = dither->error[0] / 2; | |
152 | ||
153 | /* bias */ | |
154 | output = sample + (1L << (MAD_F_FRACBITS + 1 - bits - 1)); | |
155 | ||
156 | scalebits = MAD_F_FRACBITS + 1 - bits; | |
157 | mask = (1L << scalebits) - 1; | |
158 | ||
159 | /* dither */ | |
160 | rnd = prng(dither->random); | |
161 | output += (rnd & mask) - (dither->random & mask); | |
162 | ||
163 | dither->random = rnd; | |
164 | ||
165 | /* clip */ | |
166 | if (output > MAX) { | |
167 | output = MAX; | |
168 | ||
169 | if (sample > MAX) | |
170 | sample = MAX; | |
171 | } | |
172 | else if (output < MIN) { | |
173 | output = MIN; | |
174 | ||
175 | if (sample < MIN) | |
176 | sample = MIN; | |
177 | } | |
178 | ||
179 | /* quantize */ | |
180 | output &= ~mask; | |
181 | ||
182 | /* error feedback */ | |
183 | dither->error[0] = sample - output; | |
184 | ||
185 | /* scale */ | |
186 | return output >> scalebits; | |
187 | } | |
f8f8039f RK |
188 | |
189 | /** @brief MP3 output callback */ | |
190 | static enum mad_flow mp3_output(void attribute((unused)) *data, | |
191 | struct mad_header const *header, | |
192 | struct mad_pcm *pcm) { | |
193 | size_t n = pcm->length; | |
194 | const mad_fixed_t *l = pcm->samples[0], *r = pcm->samples[1]; | |
195 | static struct audio_dither ld[1], rd[1]; | |
196 | ||
197 | output_header(header->samplerate, | |
198 | pcm->channels, | |
39492555 | 199 | 16, |
ce6c36be | 200 | 2 * pcm->channels * pcm->length, |
201 | ENDIAN_BIG); | |
f8f8039f RK |
202 | switch(pcm->channels) { |
203 | case 1: | |
204 | while(n--) | |
205 | output_16(audio_linear_dither(*l++, ld)); | |
206 | break; | |
207 | case 2: | |
208 | while(n--) { | |
209 | output_16(audio_linear_dither(*l++, ld)); | |
210 | output_16(audio_linear_dither(*r++, rd)); | |
211 | } | |
212 | break; | |
f8f8039f RK |
213 | } |
214 | return MAD_FLOW_CONTINUE; | |
215 | } | |
216 | ||
217 | /** @brief MP3 input callback */ | |
218 | static enum mad_flow mp3_input(void attribute((unused)) *data, | |
219 | struct mad_stream *stream) { | |
87b5259b | 220 | int used, remain, n; |
221 | ||
222 | /* libmad requires its caller to do ALL the buffering work, including coping | |
223 | * with partial frames. Given that it appears to be completely undocumented | |
224 | * you could perhaps be forgiven for not discovering this... */ | |
225 | if(input_count) { | |
226 | /* Compute total number of bytes consumed */ | |
227 | used = (char *)stream->next_frame - input_buffer; | |
228 | /* Compute number of bytes left to consume */ | |
229 | remain = input_count - used; | |
230 | memmove(input_buffer, input_buffer + used, remain); | |
231 | } else { | |
232 | remain = 0; | |
233 | } | |
234 | /* Read new data */ | |
11680e19 RK |
235 | n = hreader_read(input, |
236 | input_buffer + remain, | |
237 | (sizeof input_buffer) - remain); | |
87b5259b | 238 | if(n < 0) |
2e9ba080 | 239 | disorder_fatal(errno, "reading from %s", path); |
87b5259b | 240 | /* Compute total number of bytes available */ |
241 | input_count = remain + n; | |
242 | if(input_count) | |
243 | mad_stream_buffer(stream, (unsigned char *)input_buffer, input_count); | |
244 | if(n) | |
245 | return MAD_FLOW_CONTINUE; | |
246 | else | |
f8f8039f | 247 | return MAD_FLOW_STOP; |
f8f8039f RK |
248 | } |
249 | ||
f8f8039f RK |
250 | /** @brief MP3 error callback */ |
251 | static enum mad_flow mp3_error(void attribute((unused)) *data, | |
252 | struct mad_stream *stream, | |
253 | struct mad_frame attribute((unused)) *frame) { | |
39492555 | 254 | if(0) |
255 | /* Just generates pointless verbosity l-( */ | |
2e9ba080 RK |
256 | disorder_error(0, "decoding %s: %s (%#04x)", |
257 | path, mad_stream_errorstr(stream), stream->error); | |
f8f8039f RK |
258 | return MAD_FLOW_CONTINUE; |
259 | } | |
260 | ||
261 | /** @brief MP3 decoder */ | |
262 | static void decode_mp3(void) { | |
263 | struct mad_decoder mad[1]; | |
264 | ||
21237d05 RK |
265 | if(hreader_init(path, input)) |
266 | disorder_fatal(errno, "opening %s", path); | |
39492555 | 267 | mad_decoder_init(mad, 0/*data*/, mp3_input, 0/*header*/, 0/*filter*/, |
f8f8039f RK |
268 | mp3_output, mp3_error, 0/*message*/); |
269 | if(mad_decoder_run(mad, MAD_DECODER_MODE_SYNC)) | |
270 | exit(1); | |
271 | mad_decoder_finish(mad); | |
272 | } | |
273 | ||
21237d05 RK |
274 | static size_t ogg_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) { |
275 | struct hreader *h = datasource; | |
276 | ||
277 | int n = hreader_read(h, ptr, size * nmemb); | |
278 | if(n < 0) n = 0; | |
279 | return n / size; | |
280 | } | |
281 | ||
282 | static int ogg_seek_func(void *datasource, ogg_int64_t offset, int whence) { | |
283 | struct hreader *h = datasource; | |
284 | ||
285 | return hreader_seek(h, offset, whence) < 0 ? -1 : 0; | |
286 | } | |
287 | ||
288 | static int ogg_close_func(void attribute((unused)) *datasource) { | |
289 | return 0; | |
290 | } | |
291 | ||
292 | static long ogg_tell_func(void *datasource) { | |
293 | struct hreader *h = datasource; | |
294 | ||
295 | return hreader_seek(h, 0, SEEK_CUR); | |
296 | } | |
297 | ||
298 | static const ov_callbacks ogg_callbacks = { | |
299 | ogg_read_func, | |
300 | ogg_seek_func, | |
301 | ogg_close_func, | |
302 | ogg_tell_func, | |
303 | }; | |
304 | ||
572c2899 | 305 | /** @brief OGG decoder */ |
306 | static void decode_ogg(void) { | |
21237d05 | 307 | struct hreader ogginput[1]; |
572c2899 | 308 | OggVorbis_File vf[1]; |
309 | int err; | |
310 | long n; | |
311 | int bitstream; | |
312 | vorbis_info *vi; | |
313 | ||
21237d05 | 314 | hreader_init(path, ogginput); |
572c2899 | 315 | /* There doesn't seem to be any standard function for mapping the error codes |
316 | * to strings l-( */ | |
21237d05 RK |
317 | if((err = ov_open_callbacks(ogginput, vf, 0/*initial*/, 0/*ibytes*/, |
318 | ogg_callbacks))) | |
319 | disorder_fatal(0, "ov_open_callbacks %s: %d", path, err); | |
572c2899 | 320 | if(!(vi = ov_info(vf, 0/*link*/))) |
2e9ba080 | 321 | disorder_fatal(0, "ov_info %s: failed", path); |
75db8354 | 322 | while((n = ov_read(vf, input_buffer, sizeof input_buffer, 1/*bigendianp*/, |
572c2899 | 323 | 2/*bytes/word*/, 1/*signed*/, &bitstream))) { |
324 | if(n < 0) | |
2e9ba080 | 325 | disorder_fatal(0, "ov_read %s: %ld", path, n); |
572c2899 | 326 | if(bitstream > 0) |
2e9ba080 | 327 | disorder_fatal(0, "only single-bitstream ogg files are supported"); |
ce6c36be | 328 | output_header(vi->rate, vi->channels, 16/*bits*/, n, ENDIAN_BIG); |
75db8354 | 329 | if(fwrite(input_buffer, 1, n, outputfp) < (size_t)n) |
2e9ba080 | 330 | disorder_fatal(errno, "decoding %s: writing sample data", path); |
572c2899 | 331 | } |
332 | } | |
333 | ||
ce6c36be | 334 | /** @brief Sample data callback used by decode_wav() */ |
335 | static int wav_write(struct wavfile attribute((unused)) *f, | |
336 | const char *data, | |
337 | size_t nbytes, | |
338 | void attribute((unused)) *u) { | |
339 | if(fwrite(data, 1, nbytes, outputfp) < nbytes) | |
2e9ba080 | 340 | disorder_fatal(errno, "decoding %s: writing sample data", path); |
ce6c36be | 341 | return 0; |
342 | } | |
343 | ||
344 | /** @brief WAV file decoder */ | |
345 | static void decode_wav(void) { | |
346 | struct wavfile f[1]; | |
347 | int err; | |
348 | ||
349 | if((err = wav_init(f, path))) | |
2e9ba080 | 350 | disorder_fatal(err, "opening %s", path); |
ce6c36be | 351 | output_header(f->rate, f->channels, f->bits, f->datasize, ENDIAN_LITTLE); |
352 | if((err = wav_data(f, wav_write, 0))) | |
2e9ba080 | 353 | disorder_fatal(err, "error decoding %s", path); |
ce6c36be | 354 | } |
355 | ||
75db8354 | 356 | /** @brief Metadata callback for FLAC decoder |
357 | * | |
358 | * This is a no-op here. | |
359 | */ | |
7b203d74 | 360 | static void flac_metadata(const FLAC__StreamDecoder attribute((unused)) *decoder, |
75db8354 | 361 | const FLAC__StreamMetadata attribute((unused)) *metadata, |
362 | void attribute((unused)) *client_data) { | |
363 | } | |
364 | ||
365 | /** @brief Error callback for FLAC decoder */ | |
7b203d74 | 366 | static void flac_error(const FLAC__StreamDecoder attribute((unused)) *decoder, |
75db8354 | 367 | FLAC__StreamDecoderErrorStatus status, |
368 | void attribute((unused)) *client_data) { | |
2e9ba080 RK |
369 | disorder_fatal(0, "error decoding %s: %s", path, |
370 | FLAC__StreamDecoderErrorStatusString[status]); | |
75db8354 | 371 | } |
372 | ||
373 | /** @brief Write callback for FLAC decoder */ | |
374 | static FLAC__StreamDecoderWriteStatus flac_write | |
7b203d74 | 375 | (const FLAC__StreamDecoder attribute((unused)) *decoder, |
75db8354 | 376 | const FLAC__Frame *frame, |
377 | const FLAC__int32 *const buffer[], | |
378 | void attribute((unused)) *client_data) { | |
379 | size_t n, c; | |
380 | ||
381 | output_header(frame->header.sample_rate, | |
382 | frame->header.channels, | |
383 | frame->header.bits_per_sample, | |
384 | (frame->header.channels * frame->header.blocksize | |
385 | * frame->header.bits_per_sample) / 8, | |
386 | ENDIAN_BIG); | |
387 | for(n = 0; n < frame->header.blocksize; ++n) { | |
388 | for(c = 0; c < frame->header.channels; ++c) { | |
389 | switch(frame->header.bits_per_sample) { | |
390 | case 8: output_8(buffer[c][n]); break; | |
391 | case 16: output_16(buffer[c][n]); break; | |
392 | case 24: output_24(buffer[c][n]); break; | |
393 | case 32: output_32(buffer[c][n]); break; | |
394 | } | |
395 | } | |
396 | } | |
397 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; | |
398 | } | |
399 | ||
d868c56e RK |
400 | static FLAC__StreamDecoderReadStatus flac_read(const FLAC__StreamDecoder attribute((unused)) *decoder, |
401 | FLAC__byte buffer[], | |
402 | size_t *bytes, | |
403 | void *client_data) { | |
404 | struct hreader *flacinput = client_data; | |
405 | int n = hreader_read(flacinput, buffer, *bytes); | |
406 | if(n == 0) { | |
407 | *bytes = 0; | |
408 | return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; | |
409 | } | |
410 | if(n < 0) { | |
411 | *bytes = 0; | |
412 | return FLAC__STREAM_DECODER_READ_STATUS_ABORT; | |
413 | } | |
414 | *bytes = n; | |
415 | return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; | |
416 | } | |
417 | ||
418 | static FLAC__StreamDecoderSeekStatus flac_seek(const FLAC__StreamDecoder attribute((unused)) *decoder, | |
419 | FLAC__uint64 absolute_byte_offset, | |
420 | void *client_data) { | |
421 | struct hreader *flacinput = client_data; | |
422 | if(hreader_seek(flacinput, absolute_byte_offset, SEEK_SET) < 0) | |
423 | return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; | |
424 | else | |
425 | return FLAC__STREAM_DECODER_SEEK_STATUS_OK; | |
426 | } | |
427 | ||
428 | static FLAC__StreamDecoderTellStatus flac_tell(const FLAC__StreamDecoder attribute((unused)) *decoder, | |
429 | FLAC__uint64 *absolute_byte_offset, | |
430 | void *client_data) { | |
431 | struct hreader *flacinput = client_data; | |
432 | off_t offset = hreader_seek(flacinput, 0, SEEK_CUR); | |
433 | if(offset < 0) | |
434 | return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; | |
435 | *absolute_byte_offset = offset; | |
436 | return FLAC__STREAM_DECODER_TELL_STATUS_OK; | |
437 | } | |
438 | ||
439 | static FLAC__StreamDecoderLengthStatus flac_length(const FLAC__StreamDecoder attribute((unused)) *decoder, | |
440 | FLAC__uint64 *stream_length, | |
441 | void *client_data) { | |
442 | struct hreader *flacinput = client_data; | |
443 | *stream_length = hreader_size(flacinput); | |
444 | return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; | |
445 | } | |
446 | ||
447 | static FLAC__bool flac_eof(const FLAC__StreamDecoder attribute((unused)) *decoder, | |
448 | void *client_data) { | |
449 | struct hreader *flacinput = client_data; | |
450 | return hreader_eof(flacinput); | |
451 | } | |
75db8354 | 452 | |
453 | /** @brief FLAC file decoder */ | |
454 | static void decode_flac(void) { | |
05af10e9 | 455 | FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); |
762806f1 | 456 | FLAC__StreamDecoderInitStatus is; |
d868c56e | 457 | struct hreader flacinput[1]; |
762806f1 | 458 | |
05af10e9 | 459 | if (!sd) |
2e9ba080 | 460 | disorder_fatal(0, "FLAC__stream_decoder_new failed"); |
d868c56e RK |
461 | if(hreader_init(path, flacinput)) |
462 | disorder_fatal(errno, "error opening %s", path); | |
463 | ||
464 | if((is = FLAC__stream_decoder_init_stream(sd, | |
465 | flac_read, | |
466 | flac_seek, | |
467 | flac_tell, | |
468 | flac_length, | |
469 | flac_eof, | |
470 | flac_write, flac_metadata, | |
471 | flac_error, | |
472 | flacinput))) | |
473 | disorder_fatal(0, "FLAC__stream_decoder_init_stream %s: %s", | |
2e9ba080 | 474 | path, FLAC__StreamDecoderInitStatusString[is]); |
05af10e9 RK |
475 | |
476 | FLAC__stream_decoder_process_until_end_of_stream(sd); | |
477 | FLAC__stream_decoder_finish(sd); | |
478 | FLAC__stream_decoder_delete(sd); | |
75db8354 | 479 | } |
480 | ||
f8f8039f RK |
481 | /** @brief Lookup table of decoders */ |
482 | static const struct decoder decoders[] = { | |
483 | { "*.mp3", decode_mp3 }, | |
484 | { "*.MP3", decode_mp3 }, | |
572c2899 | 485 | { "*.ogg", decode_ogg }, |
486 | { "*.OGG", decode_ogg }, | |
75db8354 | 487 | { "*.flac", decode_flac }, |
488 | { "*.FLAC", decode_flac }, | |
ce6c36be | 489 | { "*.wav", decode_wav }, |
490 | { "*.WAV", decode_wav }, | |
f8f8039f RK |
491 | { 0, 0 } |
492 | }; | |
493 | ||
494 | static const struct option options[] = { | |
495 | { "help", no_argument, 0, 'h' }, | |
496 | { "version", no_argument, 0, 'V' }, | |
497 | { 0, 0, 0, 0 } | |
498 | }; | |
499 | ||
500 | /* Display usage message and terminate. */ | |
501 | static void help(void) { | |
502 | xprintf("Usage:\n" | |
503 | " disorder-decode [OPTIONS] PATH\n" | |
504 | "Options:\n" | |
505 | " --help, -h Display usage message\n" | |
506 | " --version, -V Display version number\n" | |
507 | "\n" | |
508 | "Audio decoder for DisOrder. Only intended to be used by speaker\n" | |
509 | "process, not for normal users.\n"); | |
510 | xfclose(stdout); | |
511 | exit(0); | |
512 | } | |
513 | ||
f8f8039f RK |
514 | int main(int argc, char **argv) { |
515 | int n; | |
516 | const char *e; | |
517 | ||
518 | set_progname(argv); | |
2e9ba080 | 519 | if(!setlocale(LC_CTYPE, "")) disorder_fatal(errno, "calling setlocale"); |
f8f8039f RK |
520 | while((n = getopt_long(argc, argv, "hV", options, 0)) >= 0) { |
521 | switch(n) { | |
522 | case 'h': help(); | |
3fbdc96d | 523 | case 'V': version("disorder-decode"); |
2e9ba080 | 524 | default: disorder_fatal(0, "invalid option"); |
f8f8039f RK |
525 | } |
526 | } | |
527 | if(optind >= argc) | |
2e9ba080 | 528 | disorder_fatal(0, "missing filename"); |
f8f8039f | 529 | if(optind + 1 < argc) |
2e9ba080 | 530 | disorder_fatal(0, "excess arguments"); |
f8f8039f RK |
531 | if((e = getenv("DISORDER_RAW_FD"))) { |
532 | if(!(outputfp = fdopen(atoi(e), "wb"))) | |
2e9ba080 | 533 | disorder_fatal(errno, "fdopen"); |
f8f8039f RK |
534 | } else |
535 | outputfp = stdout; | |
536 | path = argv[optind]; | |
537 | for(n = 0; | |
538 | decoders[n].pattern | |
539 | && fnmatch(decoders[n].pattern, path, 0) != 0; | |
540 | ++n) | |
541 | ; | |
542 | if(!decoders[n].pattern) | |
2e9ba080 | 543 | disorder_fatal(0, "cannot determine file type for %s", path); |
f8f8039f RK |
544 | decoders[n].decode(); |
545 | xfclose(outputfp); | |
546 | return 0; | |
547 | } | |
548 | ||
549 | /* | |
550 | Local Variables: | |
551 | c-basic-offset:2 | |
552 | comment-column:40 | |
553 | fill-column:79 | |
554 | indent-tabs-mode:nil | |
555 | End: | |
556 | */ |