oggdec.c 12.5 KB
Newer Older
1 2 3 4 5
/* OggDec
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
6
 * Copyright 2002, Michael Smith <msmith@xiph.org>
7 8 9
 *
 */

10 11 12 13
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

14 15 16 17 18 19
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <errno.h>
#include <string.h>

Michael Smith's avatar
Michael Smith committed
20 21 22 23 24
#if defined(_WIN32) || defined(__EMX__) || defined(__WATCOMC__)
#include <fcntl.h>
#include <io.h>
#endif

25 26
#include <vorbis/vorbisfile.h>

27 28
#include "i18n.h"

29 30 31
struct option long_options[] = {
    {"quiet", 0,0,'Q'},
    {"help",0,0,'h'},
32
    {"version", 0, 0, 'V'},
33 34
    {"bits", 1, 0, 'b'},
    {"endianness", 1, 0, 'e'},
35
    {"raw", 0, 0, 'R'},
36
    {"sign", 1, 0, 's'},
37
    {"output", 1, 0, 'o'},
38 39 40 41 42 43 44 45 46
    {NULL,0,0,0}
};

static int quiet = 0;
static int bits = 16;
static int endian = 0;
static int raw = 0;
static int sign = 1;
unsigned char headbuf[44]; /* The whole buffer */
47
char *outfilename = NULL;
48

49
static void version (void) {
50
    fprintf(stderr, _("oggdec from %s %s\n"), PACKAGE, VERSION);
51 52 53 54 55
}

static void usage(void)
{
    version ();
ivo's avatar
ivo committed
56 57 58 59 60
    fprintf(stdout, _(" by the Xiph.Org Foundation (http://www.xiph.org/)\n\n"));
    fprintf(stdout, _("Usage: oggdec [options] file1.ogg [file2.ogg ... fileN.ogg]\n\n"));
    fprintf(stdout, _("Supported options:\n"));
    fprintf(stdout, _(" --quiet, -Q      Quiet mode. No console output.\n"));
    fprintf(stdout, _(" --help,  -h      Produce this help message.\n"));
61
    fprintf(stdout, _(" --version, -V    Print out version number.\n"));
ivo's avatar
ivo committed
62 63
    fprintf(stdout, _(" --bits, -b       Bit depth for output (8 and 16 supported)\n"));
    fprintf(stdout, _(" --endianness, -e Output endianness for 16-bit output; 0 for\n"
64
                      "                  little endian (default), 1 for big endian.\n"));
ivo's avatar
ivo committed
65
    fprintf(stdout, _(" --sign, -s       Sign for output PCM; 0 for unsigned, 1 for\n"
66
                      "                  signed (default 1).\n"));
ivo's avatar
ivo committed
67 68
    fprintf(stdout, _(" --raw, -R        Raw (headerless) output.\n"));
    fprintf(stdout, _(" --output, -o     Output to given filename. May only be used\n"
69 70
                      "                  if there is only one input file, except in\n"
                      "                  raw mode.\n"));
71 72 73 74 75 76 77
}

static void parse_options(int argc, char **argv)
{
    int option_index = 1;
    int ret;

78
    while((ret = getopt_long(argc, argv, "QhVb:e:Rs:o:", 
79 80 81 82 83 84 85 86 87 88 89
                    long_options, &option_index)) != -1)
    {
        switch(ret)
        {
            case 'Q':
                quiet = 1;
                break;
            case 'h':
                usage();
                exit(0);
                break;
90
            case 'V':
91
                version();
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
                exit(0);
                break;
            case 's':
                sign = atoi(optarg);
                break;
            case 'b':
                bits = atoi(optarg);
                if(bits <= 8)
                    bits = 8;
                else
                    bits = 16;
                break;
            case 'e':
                endian = atoi(optarg);
                break;
107 108
            case 'o':
                outfilename = strdup(optarg);
Michael Smith's avatar
Michael Smith committed
109
                break;
110
            case 'R':
111
                raw = 1;
112 113
                break;
            default:
ivo's avatar
ivo committed
114
                fprintf(stderr, _("Internal error: Unrecognised argument\n"));
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
                break;
        }
    }
}

#define WRITE_U32(buf, x) *(buf)     = (unsigned char)((x)&0xff);\
                          *((buf)+1) = (unsigned char)(((x)>>8)&0xff);\
                          *((buf)+2) = (unsigned char)(((x)>>16)&0xff);\
                          *((buf)+3) = (unsigned char)(((x)>>24)&0xff);

#define WRITE_U16(buf, x) *(buf)     = (unsigned char)((x)&0xff);\
                          *((buf)+1) = (unsigned char)(((x)>>8)&0xff);

/* Some of this based on ao/src/ao_wav.c */
int write_prelim_header(OggVorbis_File *vf, FILE *out, ogg_int64_t knownlength) {
    unsigned int size = 0x7fffffff;
    int channels = ov_info(vf,0)->channels;
    int samplerate = ov_info(vf,0)->rate;
    int bytespersec = channels*samplerate*bits/8;
    int align = channels*bits/8;
    int samplesize = bits;

Michael Smith's avatar
Michael Smith committed
137
    if(knownlength && knownlength*bits/8*channels < size)
138
        size = (unsigned int)(knownlength*bits/8*channels+44) ;
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

    memcpy(headbuf, "RIFF", 4);
    WRITE_U32(headbuf+4, size-8);
    memcpy(headbuf+8, "WAVE", 4);
    memcpy(headbuf+12, "fmt ", 4);
    WRITE_U32(headbuf+16, 16);
    WRITE_U16(headbuf+20, 1); /* format */
    WRITE_U16(headbuf+22, channels);
    WRITE_U32(headbuf+24, samplerate);
    WRITE_U32(headbuf+28, bytespersec);
    WRITE_U16(headbuf+32, align);
    WRITE_U16(headbuf+34, samplesize);
    memcpy(headbuf+36, "data", 4);
    WRITE_U32(headbuf+40, size - 44);

    if(fwrite(headbuf, 1, 44, out) != 44) {
ivo's avatar
ivo committed
155
        fprintf(stderr, _("ERROR: Failed to write Wave header: %s\n"), strerror(errno));
156 157 158 159 160 161 162 163 164 165 166 167 168 169
        return 1;
    }

    return 0;
}

int rewrite_header(FILE *out, unsigned int written) 
{
    unsigned int length = written;

    length += 44;

    WRITE_U32(headbuf+4, length-8);
    WRITE_U32(headbuf+40, length-44);
170
    if(fseek(out, 0, SEEK_SET) != 0)
171 172 173
        return 1;

    if(fwrite(headbuf, 1, 44, out) != 44) {
ivo's avatar
ivo committed
174
        fprintf(stderr, _("ERROR: Failed to write Wave header: %s\n"), strerror(errno));
175 176 177 178 179
        return 1;
    }
    return 0;
}

180
static FILE *open_input(char *infile) 
181
{
182
    FILE *in;
183 184

    if(!infile) {
Michael Smith's avatar
Michael Smith committed
185 186 187
#ifdef __BORLANDC__
        setmode(fileno(stdin), O_BINARY);
#elif _WIN32
188 189 190 191 192 193 194
        _setmode(_fileno(stdin), _O_BINARY);
#endif
        in = stdin;
    }
    else {
        in = fopen(infile, "rb");
        if(!in) {
ivo's avatar
ivo committed
195
            fprintf(stderr, _("ERROR: Failed to open input file: %s\n"), strerror(errno));
196
            return NULL;
197 198 199
        }
    }

200 201
    return in;
}
202

203 204 205
static FILE *open_output(char *outfile) 
{
    FILE *out;
206
    if(!outfile) {
Michael Smith's avatar
Michael Smith committed
207 208 209
#ifdef __BORLANDC__
        setmode(fileno(stdout), O_BINARY);
#elif _WIN32
210 211 212 213 214 215
        _setmode(_fileno(stdout), _O_BINARY);
#endif
        out = stdout;
    }
    else {
        out = fopen(outfile, "wb");
Stan Seibert's avatar
Stan Seibert committed
216
        if(!out) {
ivo's avatar
ivo committed
217
            fprintf(stderr, _("ERROR: Failed to open output file: %s\n"), strerror(errno));
218
            return NULL;
219 220 221
        }
    }

222 223 224
    return out;
}

225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
static void
permute_channels(char *in, char *out, int len, int channels, int bytespersample)
{
    int permute[6][6] = {{0}, {0,1}, {0,2,1}, {0,1,2,3}, {0,1,2,3,4}, 
        {0,2,1,5,3,4}};
    int i,j,k;
    int samples = len/channels/bytespersample;

    /* Can't handle, don't try */
    if (channels > 6)
        return;

    for (i=0; i < samples; i++) {
        for (j=0; j < channels; j++) {
            for (k=0; k < bytespersample; k++) {
                out[i*bytespersample*channels + 
                    bytespersample*permute[channels-1][j] + k] = 
                    in[i*bytespersample*channels + bytespersample*j + k];
            }
        }
    }
}

248 249 250 251
static int decode_file(FILE *in, FILE *out, char *infile, char *outfile)
{
    OggVorbis_File vf;
    int bs = 0;
252
    char buf[8192], outbuf[8192];
253
    char *p_outbuf;
254 255 256 257 258
    int buflen = 8192;
    unsigned int written = 0;
    int ret;
    ogg_int64_t length = 0;
    ogg_int64_t done = 0;
259
    int size = 0;
260 261
    int seekable = 0;
    int percent = 0;
262 263
    int channels;
    int samplerate;
264

265
    if (ov_open_callbacks(in, &vf, NULL, 0, OV_CALLBACKS_DEFAULT) < 0) {
ivo's avatar
ivo committed
266
        fprintf(stderr, _("ERROR: Failed to open input as Vorbis\n"));
267 268 269 270
        fclose(in);
        return 1;
    }

271 272 273
    channels = ov_info(&vf,0)->channels;
    samplerate = ov_info(&vf,0)->rate;

274
    if(ov_seekable(&vf)) {
275
        int link;
276
        int chainsallowed = 0;
277
        for(link = 0; link < ov_streams(&vf); link++) {
Michael Smith's avatar
Michael Smith committed
278
            if(ov_info(&vf, link)->channels == channels && 
279 280 281 282 283 284
                    ov_info(&vf, link)->rate == samplerate)
            {
                chainsallowed = 1;
            }
        }

285
        seekable = 1;
286 287 288 289 290
        if(chainsallowed)
            length = ov_pcm_total(&vf, -1);
        else
            length = ov_pcm_total(&vf, 0);
        size = bits/8 * channels;
291
        if(!quiet)
292 293 294
            fprintf(stderr, _("Decoding \"%s\" to \"%s\"\n"), 
                    infile?infile:_("standard input"), 
                    outfile?outfile:_("standard output"));
295 296 297 298 299 300 301 302 303 304 305
    }

    if(!raw) {
        if(write_prelim_header(&vf, out, length)) {
            ov_clear(&vf);
            return 1;
        }
    }

    while((ret = ov_read(&vf, buf, buflen, endian, bits/8, sign, &bs)) != 0) {
        if(bs != 0) {
306 307
            vorbis_info *vi = ov_info(&vf, -1);
            if(channels != vi->channels || samplerate != vi->rate) {
ivo's avatar
ivo committed
308
                fprintf(stderr, _("Logical bitstreams with changing parameters are not supported\n"));
309 310
                break;
            }
311 312
        }

313 314
        if(ret < 0 ) {
           if( !quiet ) {
ivo's avatar
ivo committed
315
               fprintf(stderr, _("WARNING: hole in data (%d)\n"), ret);
316
           }
317 318 319
            continue;
        }

320 321 322
        if(channels > 2 && !raw) {
          /* Then permute! */
          permute_channels(buf, outbuf, ret, channels, bits/8);
323 324 325 326
          p_outbuf = outbuf;
        }
        else {
          p_outbuf = buf;
327 328
        }

329
        if(fwrite(p_outbuf, 1, ret, out) != ret) {
ivo's avatar
ivo committed
330
            fprintf(stderr, _("Error writing to file: %s\n"), strerror(errno));
331 332 333 334 335 336 337 338
            ov_clear(&vf);
            return 1;
        }

        written += ret;
        if(!quiet && seekable) {
            done += ret/size;
            if((double)done/(double)length * 200. > (double)percent) {
sping's avatar
sping committed
339
                percent = (int)((double)done/(double)length *200);
340 341 342 343 344 345 346 347
                fprintf(stderr, "\r\t[%5.1f%%]", (double)percent/2.);
            }
        }
    }

    if(seekable && !quiet)
        fprintf(stderr, "\n");

348
    if(!raw)
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
        rewrite_header(out, written); /* We don't care if it fails, too late */

    ov_clear(&vf);

    return 0;
}

int main(int argc, char **argv)
{
    int i;

    if(argc == 1) {
        usage();
        return 1;
    }

    parse_options(argc,argv);

    if(!quiet)
368
        version();
369 370

    if(optind >= argc) {
ivo's avatar
ivo committed
371
        fprintf(stderr, _("ERROR: No input files specified. Use -h for help\n"));
372 373 374
        return 1;
    }

375
    if(argc - optind > 1 && outfilename && !raw) {
ivo's avatar
ivo committed
376
        fprintf(stderr, _("ERROR: Can only specify one input file if output filename is specified\n"));
377 378
        return 1;
    }
379 380 381 382 383 384

    if(outfilename && raw) {
        FILE *infile, *outfile;
        char *infilename;

        if(!strcmp(outfilename, "-")) {
385
            free(outfilename);
386 387
            outfilename = NULL;
        }
388
        outfile = open_output(outfilename);
389 390 391

        if(!outfile)
            return 1;
392

393 394 395 396 397 398 399 400 401 402 403 404
        for(i=optind; i < argc; i++) {
            if(!strcmp(argv[i], "-")) {
                infilename = NULL;
                infile = open_input(NULL);
            }
            else {
                infilename = argv[i];
                infile = open_input(argv[i]);
            }

            if(!infile) {
                fclose(outfile);
405
                free(outfilename);
406 407 408 409 410 411
                return 1;
            }
            if(decode_file(infile, outfile, infilename, outfilename)) {
                fclose(outfile);
                return 1;
            }
412 413

        }
414 415 416 417 418 419 420 421 422 423

        fclose(outfile);
    }
    else {
        for(i=optind; i < argc; i++) {
            char *in, *out;
            FILE *infile, *outfile;

            if(!strcmp(argv[i], "-"))
                in = NULL;
424
            else
425
                in = argv[i];
426

427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
            if(outfilename) {
                if(!strcmp(outfilename, "-"))
                    out = NULL;
                else
                    out = outfilename;
            }
            else {
                char *end = strrchr(argv[i], '.');
                end = end?end:(argv[i] + strlen(argv[i]) + 1);

                out = malloc(strlen(argv[i]) + 10);
                strncpy(out, argv[i], end-argv[i]);
                out[end-argv[i]] = 0;
                if(raw)
                    strcat(out, ".raw");
                else
                    strcat(out, ".wav");
            }

            infile = open_input(in);
447 448 449
            if(!infile) {
                if(outfilename)
                    free(outfilename);
450
                return 1;
451
            }
452 453 454 455 456
            outfile = open_output(out);
            if(!outfile) {
                fclose(infile);
                return 1;
            }
457

458 459 460 461 462
            if(decode_file(infile, outfile, in, out)) {
                fclose(outfile);
                return 1;
            }

463 464 465
            if(!outfilename)
                free(out);

466 467
            fclose(outfile);
        }
468 469
    }

Michael Smith's avatar
Michael Smith committed
470 471 472
    if(outfilename)
        free(outfilename);

473 474
    return 0;
}