/* * ALAC (Apple Lossless Audio Codec) decoder * Copyright (c) 2005 David Hammerton * All rights reserved. * * This is simply the glue for everything. It asks the demuxer to * parse the quicktime container, asks the decoder to decode * the data and writes output as either RAW PCM or WAV. * * http://crazney.net/programs/itunes/alac.html * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * */ #include #include #include #include #include #include #ifdef WIN32 #include #endif #include "demux.h" #include "decomp.h" #include "stream.h" #include "wavwriter.h" int host_bigendian = 0; alac_file *alac = NULL; static FILE *input_file = NULL; static int input_opened = 0; static stream_t *input_stream; static FILE *output_file = NULL; static int output_opened = 0; static int write_wav_format = 1; static int verbose = 0; static int test_file_type = 0; static int get_sample_info(demux_res_t *demux_res, uint32_t samplenum, uint32_t *sample_duration, uint32_t *sample_byte_size) { unsigned int duration_index_accum = 0; unsigned int duration_cur_index = 0; if (samplenum >= demux_res->num_sample_byte_sizes) { fprintf(stderr, "sample %i does not exist\n", samplenum); return 0; } if (!demux_res->num_time_to_samples) { fprintf(stderr, "no time to samples\n"); return 0; } while ((demux_res->time_to_sample[duration_cur_index].sample_count + duration_index_accum) <= samplenum) { duration_index_accum += demux_res->time_to_sample[duration_cur_index].sample_count; duration_cur_index++; if (duration_cur_index >= demux_res->num_time_to_samples) { fprintf(stderr, "sample %i does not have a duration\n", samplenum); return 0; } } *sample_duration = demux_res->time_to_sample[duration_cur_index].sample_duration; *sample_byte_size = demux_res->sample_byte_size[samplenum]; return 1; } static void GetBuffer(demux_res_t *demux_res) { unsigned long destBufferSize = 1024*16; /* 16kb buffer = 4096 frames = 1 alac sample */ void *pDestBuffer = malloc(destBufferSize); int bytes_read = 0; unsigned int buffer_size = 1024*64; void *buffer; unsigned int i; buffer = malloc(buffer_size); for (i = 0; i < demux_res->num_sample_byte_sizes; i++) { uint32_t sample_duration; uint32_t sample_byte_size; int outputBytes; /* just get one sample for now */ if (!get_sample_info(demux_res, i, &sample_duration, &sample_byte_size)) { fprintf(stderr, "sample failed\n"); return; } if (buffer_size < sample_byte_size) { fprintf(stderr, "sorry buffer too small! (is %i want %i)\n", buffer_size, sample_byte_size); return; } stream_read(input_stream, sample_byte_size, buffer); /* now fetch */ outputBytes = destBufferSize; decode_frame(alac, buffer, pDestBuffer, &outputBytes); /* write */ bytes_read += outputBytes; if (verbose) fprintf(stderr, "read %i bytes. total: %i\n", outputBytes, bytes_read); fwrite(pDestBuffer, outputBytes, 1, output_file); if (ferror(output_file)) { perror("GetBuffer fwrite error"); break; } } if (verbose) fprintf(stderr, "done reading, read %i frames\n", i); } static void init_sound_converter(demux_res_t *demux_res) { alac = create_alac(demux_res->sample_size, demux_res->num_channels); alac_set_info(alac, demux_res->codecdata); } static void usage() { fprintf(stderr, "Usage: alac [options] [--] file\n" "Decompresses the ALAC file specified\n" "\n" "Options:\n" " -f output.wav outputs the decompressed data to the\n" " specified file, in WAV format. Default\n" " is stdout.\n" " -r write output as raw PCM data. Default\n" " is in WAV format.\n" " -v verbose output.\n" " -t test that file is ALAC, also tests for\n" " other m4a file types.\n" "\n" "This software is Copyright (c) 2005 David Hammerton\n" "All rights reserved - V 0.patched for Slimserver\n" "http://crazney.net/\n"); exit(1); } static void setup_environment(int argc, char **argv) { int i = argc; char *input_file_n = NULL; char *output_file_n = NULL; int escaped = 0; if (argc < 2) usage(); for (i = argc-1; i > 1; i--) { if (strcmp(argv[argc-i], "-f") == 0) /* output file */ { if (!--i) usage(); output_file_n = argv[argc-i]; } else if (strcmp(argv[argc-i], "-r") == 0) /* raw PCM output */ { write_wav_format = 0; } else if (strcmp(argv[argc-i], "-v") == 0) /* verbose */ { verbose = 1; } else if (strcmp(argv[argc-i], "-t") == 0) /* test file type */ { test_file_type = 1; } else if (strcmp(argv[argc-i], "--") == 0) /* filename can begin with - */ { /* must be 2nd last arg */ if (i != 2) usage(); escaped = 1; } else usage(); } if (i != 1) usage(); input_file_n = argv[argc-1]; /* if the final parameter begins with '-', and isn't just "-", * it's probably an unsupported argument, print usage and exit. * (Unless it's been escaped with --) */ if (!escaped && input_file_n[0] == '-' && input_file_n[1] != 0) usage(); if (!input_file_n) usage(); if (output_file_n) { output_file = fopen(output_file_n, "wb"); if (!output_file) { fprintf(stderr, "failed to open output file '%s': ", output_file_n); perror(NULL); exit(1); } output_opened = 1; } else { /* defaults to stdout */ output_file = stdout; } if (strcmp(input_file_n, "-") == 0) { input_file = stdin; } else { input_file = fopen(input_file_n, "rb"); if (!input_file) { fprintf(stderr, "failed to open input file '%s': ", input_file_n); perror(NULL); exit(1); } input_opened = 1; } } /* this could quite easily be done at compile time, * however I don't want to have to bother with all the * various possible #define's for endianness, worrying about * different compilers etc. and I'm too lazy to use autoconf. */ void set_endian() { uint32_t integer = 0x000000aa; unsigned char *p = (unsigned char*)&integer; if (p[0] == 0xaa) host_bigendian = 0; else host_bigendian = 1; } int main(int argc, char **argv) { demux_res_t demux_res; unsigned int output_size, i; memset(&demux_res, 0, sizeof(demux_res)); set_endian(); setup_environment(argc, argv); #ifdef WIN32 setmode(fileno(stdout), O_BINARY); setmode(fileno(stdin), O_BINARY); #endif input_stream = stream_create_file(input_file, 1); if (!input_stream) { fprintf(stderr, "failed to create input stream from file\n"); return 1; } /* if qtmovie_read returns successfully, the stream is up to * the movie data, which can be used directly by the decoder */ if (!qtmovie_read(input_stream, &demux_res)) { if (!test_file_type || !demux_res.format_read) { fprintf(stderr, "failed to load the QuickTime movie headers"); if (demux_res.format_read) fprintf(stderr, " (file type: %c%c%c%c)\n", SPLITFOURCC(demux_res.format)); else fprintf(stderr, "\n"); return 1; } } if (test_file_type) { /* just in case: */ if (!demux_res.format_read) { fprintf(stderr, "failed to load the QUickTime movie headers." " Probably not a quicktime file\n"); return 1; } printf("file type: %c%c%c%c\n", SPLITFOURCC(demux_res.format)); /* now, we have to return useful return codes */ switch (demux_res.format) { case MAKEFOURCC('a','l','a','c'): return 0; /* alac = 0 */ case MAKEFOURCC('m','p','4','a'): return 100; /* m4pa = unencrypted aac = 100 */ } return 1; } /* initialise the sound converter */ init_sound_converter(&demux_res); /* write wav output headers */ if (write_wav_format) { /* calculate output size */ output_size = 0; for (i = 0; i < demux_res.num_sample_byte_sizes; i++) { unsigned int thissample_duration = 0; unsigned int thissample_bytesize = 0; get_sample_info(&demux_res, i, &thissample_duration, &thissample_bytesize); output_size += thissample_duration * (demux_res.sample_size / 8) * demux_res.num_channels; } wavwriter_writeheaders(output_file, output_size, demux_res.num_channels, demux_res.sample_rate, demux_res.sample_size); } /* will convert the entire buffer */ GetBuffer(&demux_res); stream_destroy(input_stream); if (output_opened) fclose(output_file); if (input_opened) fclose(input_file); return 0; }