From 7617cacb282a00c0188aca3fec5613aa1d038740 Mon Sep 17 00:00:00 2001 From: Josh Coalson Date: Tue, 9 Sep 2008 07:24:23 +0000 Subject: [PATCH] add support for RF64 --- doc/html/changelog.html | 2 + doc/html/documentation_tools_flac.html | 37 +++-- man/flac.sgml | 13 +- src/flac/decode.c | 261 +++++++++++++++------------------ src/flac/decode.h | 28 ++-- src/flac/encode.c | 160 +++++++++++++++----- src/flac/foreign_metadata.c | 138 ++++++++++++++--- src/flac/foreign_metadata.h | 13 +- src/flac/iffscan.c | 27 +++- src/flac/main.c | 130 +++++++++------- src/flac/utils.c | 10 ++ src/flac/utils.h | 5 +- src/test_streams/main.c | 93 +++++++++++- test/Makefile.am | 5 +- test/Makefile.lite | 2 +- test/test_flac.sh | 54 +++++-- test/write_iff.pl | 168 +++++++++++++++++++++ 17 files changed, 843 insertions(+), 303 deletions(-) create mode 100755 test/write_iff.pl diff --git a/doc/html/changelog.html b/doc/html/changelog.html index 3e14ccb..2f213c8 100644 --- a/doc/html/changelog.html +++ b/doc/html/changelog.html @@ -63,6 +63,7 @@ General:
  • @@ -80,6 +81,7 @@
  • flac: diff --git a/doc/html/documentation_tools_flac.html b/doc/html/documentation_tools_flac.html index 19ed38f..aafc37c 100644 --- a/doc/html/documentation_tools_flac.html +++ b/doc/html/documentation_tools_flac.html @@ -63,9 +63,9 @@ General Usage

    - flac is the command-line file encoder/decoder. The encoder currently supports as input RIFF WAVE, AIFF, FLAC or Ogg FLAC format, or raw interleaved samples. The decoder currently can output to RIFF WAVE or AIFF format, or raw interleaved samples. flac only supports linear PCM samples (in other words, no A-LAW, uLAW, etc.), and the input must be between 4 and 24 bits per sample. This is not a limitation of the FLAC format, just the reference encoder/decoder.
    + flac is the command-line file encoder/decoder. The encoder currently supports as input RIFF WAVE, RF64, AIFF, FLAC or Ogg FLAC format, or raw interleaved samples. The decoder currently can output to RIFF WAVE, RF64, or AIFF format, or raw interleaved samples. flac only supports linear PCM samples (in other words, no A-LAW, uLAW, etc.), and the input must be between 4 and 24 bits per sample. This is not a limitation of the FLAC format, just the reference encoder/decoder.

    - flac assumes that files ending in ".wav" or that have the RIFF WAVE header present are WAVE files, files ending in ".aif" or ".aiff" or have the AIFF header present are AIFF files, and files ending in ".flac" or have the FLAC header present are FLAC files. This assumption may be overridden with a command-line option. It also assumes that files ending in ".oga" or ".ogg" or have the Ogg FLAC header present are Ogg FLAC files. Other than this, flac makes no assumptions about file extensions, though the convention is that FLAC files have the extension ".flac" (or ".fla" on ancient "8.3" file systems like FAT-16).
    + flac assumes that files ending in ".wav" or that have the RIFF WAVE header present are WAVE files, files ending in ".rf64" or have the RF64 header present are RF64 files, files ending in ".aif" or ".aiff" or have the AIFF header present are AIFF files, and files ending in ".flac" or have the FLAC header present are FLAC files. This assumption may be overridden with a command-line option. It also assumes that files ending in ".oga" or ".ogg" or have the Ogg FLAC header present are Ogg FLAC files. Other than this, flac makes no assumptions about file extensions, though the convention is that FLAC files have the extension ".flac" (or ".fla" on ancient "8.3" file systems like FAT-16).

    Before going into the full command-line description, a few other things help to sort it out: 1) flac encodes by default, so you must use -d to decode; 2) the options -0 .. -8 (or --fast and --best) that control the compression level actually are just synonyms for different groups of specific encoding options (described later) and you can get the same effect by using the same options; 3) flac behaves similarly to gzip in the way it handles input and output files.

    @@ -116,7 +116,7 @@ flac -V -- -01-filename.wav
  • - The encoding options affect the compression ratio and encoding speed. The format options are used to tell flac the arrangement of samples if the input file (or output file when decoding) is a raw file. If it is a RIFF WAVE or AIFF file the format options are not needed since they are read from the AIFF/WAVE header.
    + The encoding options affect the compression ratio and encoding speed. The format options are used to tell flac the arrangement of samples if the input file (or output file when decoding) is a raw file. If it is a RIFF WAVE, RF64, or AIFF file the format options are not needed since they are read from the file's header.

    In test mode, flac acts just like in decode mode, except no output file is written. Both decode and test modes detect errors in the stream, but they also detect when the MD5 signature of the decoded audio does not match the stored MD5 signature, even when the bitstream is valid.

    @@ -156,6 +156,9 @@ flac abc.aiff
    Encode abc.aiff to abc.flac.

    + flac abc.rf64
    + Encode abc.rf64 to abc.flac.
    +
    flac abc.flac --force
    This one's a little tricky: notice that flac is in encode mode by default (you have to specify -d to decode) so this command actually recompresses abc.flac back to abc.flac. --force is needed to make sure you really want to overwrite abc.flac with a new version. Why would you want to do this? It allows you to recompress an existing FLAC file with (usually) higher compression options or a newer version of FLAC and preserve all the metadata like tags too.

    @@ -169,6 +172,10 @@ flac -d -o abc.aiff abc.flac
    Two different ways of decoding abc.flac to abc.aiff (AIFF format). abc.flac is not deleted.

    + flac -d --force-rf64-format abc.flac
    + flac -d -o abc.rf64 abc.flac
    + Two different ways of decoding abc.flac to abc.rf64 (RF64 format). abc.flac is not deleted.
    +
    flac -d -F abc.flac
    Decode abc.flac to abc.wav and don't abort if errors are found (useful for recovering as much as possible from corrupted files).

    @@ -332,15 +339,15 @@ --keep-foreign-metadata - If encoding, save WAVE or AIFF non-audio chunks in FLAC metadata. If decoding, restore any saved non-audio chunks from FLAC metadata when writing the decoded file. Foreign metadata cannot be transcoded, e.g. WAVE chunks saved in a FLAC file cannot be restored when decoding to AIFF. Input and output must be regular files (not stdin or stdout).
    + If encoding, save WAVE, RF64, or AIFF non-audio chunks in FLAC metadata. If decoding, restore any saved non-audio chunks from FLAC metadata when writing the decoded file. Foreign metadata cannot be transcoded, e.g. WAVE chunks saved in a FLAC file cannot be restored when decoding to AIFF. Input and output must be regular files (not stdin or stdout).
    @@ -946,7 +953,16 @@ --force-aiff-format - Force the decoder to output AIFF format. This option is not needed if the output filename (as set by -o) ends with .aiff. Also, this option has no effect when encoding since input AIFF is auto-detected. + Force the decoder to output AIFF format. This option is not needed if the output filename (as set by -o) ends with .aif or .aiff. Also, this option has no effect when encoding since input AIFF is auto-detected. + + + + + + --force-rf64-format + + + Force the decoder to output RF64 format. This option is not needed if the output filename (as set by -o) ends with .rf64. Also, this option has no effect when encoding since input RF64 is auto-detected. @@ -1046,6 +1062,7 @@ -f
    --fast
    --force-aiff-format
    + --force-rf64-format
    --force-raw-format
    --force
    -H
    diff --git a/man/flac.sgml b/man/flac.sgml index 7572a73..317afd1 100644 --- a/man/flac.sgml +++ b/man/flac.sgml @@ -48,6 +48,7 @@ OPTIONS infile.wav + infile.rf64 infile.aiff infile.raw infile.flac @@ -217,7 +218,7 @@ - If encoding, save WAVE or AIFF non-audio chunks in FLAC metadata. If decoding, restore any saved non-audio chunks from FLAC metadata when writing the decoded file. Foreign metadata cannot be transcoded, e.g. WAVE chunks saved in a FLAC file cannot be restored when decoding to AIFF. Input and output must be regular files (not stdin or stdout). + If encoding, save WAVE, RF64, or AIFF non-audio chunks in FLAC metadata. If decoding, restore any saved non-audio chunks from FLAC metadata when writing the decoded file. Foreign metadata cannot be transcoded, e.g. WAVE chunks saved in a FLAC file cannot be restored when decoding to AIFF. Input and output must be regular files (not stdin or stdout). @@ -655,7 +656,15 @@ - Force the decoder to output AIFF format. This option is not needed if the output filename (as set by -o) ends with .aiff. Also, this option has no effect when encoding since input AIFF is auto-detected. + Force the decoder to output AIFF format. This option is not needed if the output filename (as set by -o) ends with .aif or .aiff. Also, this option has no effect when encoding since input AIFF is auto-detected. + + + + + + + + Force the decoder to output RF64 format. This option is not needed if the output filename (as set by -o) ends with .rf64. Also, this option has no effect when encoding since input RF64 is auto-detected. diff --git a/src/flac/decode.c b/src/flac/decode.c index 05b1f0e..d98cf6a 100644 --- a/src/flac/decode.c +++ b/src/flac/decode.c @@ -49,8 +49,7 @@ typedef struct { long serial_number; #endif - FLAC__bool is_aiff_out; - FLAC__bool is_wave_out; + FileFormat format; FLAC__bool treat_warnings_as_errors; FLAC__bool continue_through_decode_errors; FLAC__bool channel_map_none; @@ -109,7 +108,7 @@ static FLAC__bool is_big_endian_host_; /* * local routines */ -static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool use_first_serial_number, long serial_number, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool treat_warnings_as_errors, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, foreign_metadata_t *foreign_metadata, const char *infilename, const char *outfilename); +static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool use_first_serial_number, long serial_number, FileFormat format, FLAC__bool treat_warnings_as_errors, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, foreign_metadata_t *foreign_metadata, const char *infilename, const char *outfilename); static void DecoderSession_destroy(DecoderSession *d, FLAC__bool error_occurred); static FLAC__bool DecoderSession_init_decoder(DecoderSession *d, const char *infilename); static FLAC__bool DecoderSession_process(DecoderSession *d); @@ -121,6 +120,7 @@ static FLAC__bool write_riff_wave_fmt_chunk(FILE *f, FLAC__bool is_waveformatext static FLAC__bool write_aiff_form_comm_chunk(FILE *f, FLAC__uint64 samples, unsigned bps, unsigned channels, unsigned sample_rate); static FLAC__bool write_little_endian_uint16(FILE *f, FLAC__uint16 val); static FLAC__bool write_little_endian_uint32(FILE *f, FLAC__uint32 val); +static FLAC__bool write_little_endian_uint64(FILE *f, FLAC__uint64 val); static FLAC__bool write_big_endian_uint16(FILE *f, FLAC__uint16 val); static FLAC__bool write_big_endian_uint32(FILE *f, FLAC__uint32 val); static FLAC__bool write_sane_extended(FILE *f, unsigned val); @@ -136,123 +136,46 @@ static void print_stats(const DecoderSession *decoder_session); /* * public routines */ -int flac__decode_aiff(const char *infilename, const char *outfilename, FLAC__bool analysis_mode, analysis_options aopts, wav_decode_options_t options) +int flac__decode_file(const char *infilename, const char *outfilename, FLAC__bool analysis_mode, analysis_options aopts, decode_options_t options) { DecoderSession decoder_session; - if(! - DecoderSession_construct( - &decoder_session, -#if FLAC__HAS_OGG - options.common.is_ogg, - options.common.use_first_serial_number, - options.common.serial_number, -#else - /*is_ogg=*/false, - /*use_first_serial_number=*/false, - /*serial_number=*/0, -#endif - /*is_aiff_out=*/true, - /*is_wave_out=*/false, - options.common.treat_warnings_as_errors, - options.common.continue_through_decode_errors, - options.common.channel_map_none, - options.common.replaygain_synthesis_spec, - analysis_mode, - aopts, - &options.common.skip_specification, - &options.common.until_specification, - options.common.has_cue_specification? &options.common.cue_specification : 0, - options.foreign_metadata, - infilename, - outfilename - ) - ) - return 1; - - if(!DecoderSession_init_decoder(&decoder_session, infilename)) - return DecoderSession_finish_error(&decoder_session); - - if(!DecoderSession_process(&decoder_session)) - return DecoderSession_finish_error(&decoder_session); - - return DecoderSession_finish_ok(&decoder_session); -} - -int flac__decode_wav(const char *infilename, const char *outfilename, FLAC__bool analysis_mode, analysis_options aopts, wav_decode_options_t options) -{ - DecoderSession decoder_session; - - if(! - DecoderSession_construct( - &decoder_session, -#if FLAC__HAS_OGG - options.common.is_ogg, - options.common.use_first_serial_number, - options.common.serial_number, -#else - /*is_ogg=*/false, - /*use_first_serial_number=*/false, - /*serial_number=*/0, -#endif - /*is_aiff_out=*/false, - /*is_wave_out=*/true, - options.common.treat_warnings_as_errors, - options.common.continue_through_decode_errors, - options.common.channel_map_none, - options.common.replaygain_synthesis_spec, - analysis_mode, - aopts, - &options.common.skip_specification, - &options.common.until_specification, - options.common.has_cue_specification? &options.common.cue_specification : 0, - options.foreign_metadata, - infilename, - outfilename - ) - ) - return 1; - - if(!DecoderSession_init_decoder(&decoder_session, infilename)) - return DecoderSession_finish_error(&decoder_session); - - if(!DecoderSession_process(&decoder_session)) - return DecoderSession_finish_error(&decoder_session); - - return DecoderSession_finish_ok(&decoder_session); -} - -int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool analysis_mode, analysis_options aopts, raw_decode_options_t options) -{ - DecoderSession decoder_session; - - decoder_session.is_big_endian = options.is_big_endian; - decoder_session.is_unsigned_samples = options.is_unsigned_samples; + FLAC__ASSERT( + options.format == FORMAT_WAVE || + options.format == FORMAT_RF64 || + options.format == FORMAT_AIFF || + options.format == FORMAT_AIFF_C || + options.format == FORMAT_RAW + ); + + if(options.format == FORMAT_RAW) { + decoder_session.is_big_endian = options.format_options.raw.is_big_endian; + decoder_session.is_unsigned_samples = options.format_options.raw.is_unsigned_samples; + } if(! DecoderSession_construct( &decoder_session, #if FLAC__HAS_OGG - options.common.is_ogg, - options.common.use_first_serial_number, - options.common.serial_number, + options.is_ogg, + options.use_first_serial_number, + options.serial_number, #else /*is_ogg=*/false, /*use_first_serial_number=*/false, /*serial_number=*/0, #endif - /*is_aiff_out=*/false, - /*is_wave_out=*/false, - options.common.treat_warnings_as_errors, - options.common.continue_through_decode_errors, - options.common.channel_map_none, - options.common.replaygain_synthesis_spec, + options.format, + options.treat_warnings_as_errors, + options.continue_through_decode_errors, + options.channel_map_none, + options.replaygain_synthesis_spec, analysis_mode, aopts, - &options.common.skip_specification, - &options.common.until_specification, - options.common.has_cue_specification? &options.common.cue_specification : 0, - /*foreign_metadata=*/NULL, + &options.skip_specification, + &options.until_specification, + options.has_cue_specification? &options.cue_specification : 0, + options.format == FORMAT_RAW? NULL : options.format_options.iff.foreign_metadata, infilename, outfilename ) @@ -268,7 +191,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool return DecoderSession_finish_ok(&decoder_session); } -FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool use_first_serial_number, long serial_number, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool treat_warnings_as_errors, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, foreign_metadata_t *foreign_metadata, const char *infilename, const char *outfilename) +FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool use_first_serial_number, long serial_number, FileFormat format, FLAC__bool treat_warnings_as_errors, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, foreign_metadata_t *foreign_metadata, const char *infilename, const char *outfilename) { #if FLAC__HAS_OGG d->is_ogg = is_ogg; @@ -280,8 +203,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__ (void)serial_number; #endif - d->is_aiff_out = is_aiff_out; - d->is_wave_out = is_wave_out; + d->format = format; d->treat_warnings_as_errors = treat_warnings_as_errors; d->continue_through_decode_errors = continue_through_decode_errors; d->channel_map_none = channel_map_none; @@ -361,13 +283,11 @@ FLAC__bool DecoderSession_init_decoder(DecoderSession *decoder_session, const ch is_big_endian_host_ = (*((FLAC__byte*)(&test)))? false : true; - if(!decoder_session->analysis_mode && !decoder_session->test_only && (decoder_session->is_wave_out || decoder_session->is_aiff_out)) { - if(decoder_session->foreign_metadata) { - const char *error; - if(!flac__foreign_metadata_read_from_flac(decoder_session->foreign_metadata, infilename, &error)) { - flac__utils_printf(stderr, 1, "%s: ERROR reading foreign metadata: %s\n", decoder_session->inbasefilename, error); - return false; - } + if(!decoder_session->analysis_mode && !decoder_session->test_only && decoder_session->foreign_metadata) { + const char *error; + if(!flac__foreign_metadata_read_from_flac(decoder_session->foreign_metadata, infilename, &error)) { + flac__utils_printf(stderr, 1, "%s: ERROR reading foreign metadata: %s\n", decoder_session->inbasefilename, error); + return false; } } @@ -452,7 +372,7 @@ FLAC__bool DecoderSession_process(DecoderSession *d) } /* write the WAVE/AIFF headers if necessary */ - if(!d->analysis_mode && !d->test_only && (d->is_wave_out || d->is_aiff_out)) { + if(!d->analysis_mode && !d->test_only && d->format != FORMAT_RAW) { if(!write_iff_headers(d->fout, d, d->total_samples)) { d->abort_flag = true; return false; @@ -482,9 +402,9 @@ FLAC__bool DecoderSession_process(DecoderSession *d) return false; } - if(!d->analysis_mode && !d->test_only && (d->is_wave_out || d->is_aiff_out) && ((d->total_samples * d->channels * ((d->bps+7)/8)) & 1)) { + if(!d->analysis_mode && !d->test_only && d->format != FORMAT_RAW && ((d->total_samples * d->channels * ((d->bps+7)/8)) & 1)) { if(flac__utils_fwrite("\000", 1, 1, d->fout) != 1) { - print_error_with_state(d, d->is_wave_out? + print_error_with_state(d, d->format == FORMAT_WAVE || d->format == FORMAT_RF64? "ERROR writing pad byte to WAVE data chunk" : "ERROR writing pad byte to AIFF SSND chunk" ); @@ -522,7 +442,7 @@ int DecoderSession_finish_ok(DecoderSession *d) flac__utils_printf(stderr, 2, "\r%s: %s \n", d->inbasefilename, d->test_only? "ok ":d->analysis_mode?"done ":"done"); } DecoderSession_destroy(d, /*error_occurred=*/!ok); - if(!d->analysis_mode && !d->test_only && (d->is_wave_out || d->is_aiff_out)) { + if(!d->analysis_mode && !d->test_only && d->format != FORMAT_RAW) { if(d->iff_headers_need_fixup || (!d->got_stream_info && strcmp(d->outfilename, "-"))) { if(!fixup_iff_headers(d)) return 1; @@ -597,15 +517,20 @@ FLAC__bool canonicalize_until_specification(utils__SkipUntilSpecification *spec, FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uint64 samples) { - const char *fmt_desc = decoder_session->is_wave_out? "WAVE" : "AIFF"; - const FLAC__bool is_waveformatextensible = decoder_session->is_wave_out && (decoder_session->channel_mask == 2 || decoder_session->channel_mask > 3 || decoder_session->bps%8 || decoder_session->channels > 2); - FLAC__uint64 data_size = samples * decoder_session->channels * ((decoder_session->bps+7)/8); - const FLAC__uint32 aligned_data_size = (FLAC__uint32)((data_size+1) & (~1U)); /* we'll check for overflow later */ - + const FileFormat format = decoder_session->format; + const char *fmt_desc = format==FORMAT_WAVE? "WAVE" : format==FORMAT_RF64? "RF64" : "AIFF"; + const FLAC__bool is_wavish = format == FORMAT_WAVE || format == FORMAT_RF64; + const FLAC__bool is_waveformatextensible = is_wavish && (decoder_session->channel_mask == 2 || decoder_session->channel_mask > 3 || decoder_session->bps%8 || decoder_session->channels > 2); + const FLAC__uint64 data_size = samples * decoder_session->channels * ((decoder_session->bps+7)/8); + const FLAC__uint64 aligned_data_size = data_size & 1? (data_size+1) : data_size; + + FLAC__uint64 iff_size; unsigned foreign_metadata_size = 0; /* size of all non-audio non-fmt/COMM foreign metadata chunks */ foreign_metadata_t *fm = decoder_session->foreign_metadata; size_t i; + FLAC__ASSERT(flac__utils_format_is_iff(format)); + if(samples == 0) { if(f == stdout) { flac__utils_printf(stderr, 1, "%s: WARNING, don't have accurate sample count available for %s header.\n", decoder_session->inbasefilename, fmt_desc); @@ -623,33 +548,70 @@ FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uin FLAC__ASSERT(fm->format_block); FLAC__ASSERT(fm->audio_block); FLAC__ASSERT(fm->format_block < fm->audio_block); - /* calc foreign metadata size; for RIFF/AIFF we always skip the first chunk, format chunk, and sound chunk since we write our own */ - for(i = 1; i < fm->num_blocks; i++) { + /* calc foreign metadata size; for RIFF/AIFF we always skip the first chunk, ds64 chunk, format chunk, and sound chunk since we write our own */ + for(i = format==FORMAT_RF64?2:1; i < fm->num_blocks; i++) { if(i != fm->format_block && i != fm->audio_block) foreign_metadata_size += fm->blocks[i].size; } } - if(data_size + foreign_metadata_size + 60/*worst-case*/ >= 0xFFFFFFF4) { + if(samples == 0) + iff_size = 0; + else if(is_wavish) + iff_size = (is_waveformatextensible?60:36) + (format==FORMAT_RF64?36:0) + foreign_metadata_size + aligned_data_size; + else + /* @@@@@@ can ssnd_offset_size be odd and hence screw up our alignment logic? */ + iff_size = 46 + foreign_metadata_size + aligned_data_size + (fm? fm->ssnd_offset_size : 0); + + if(format != FORMAT_RF64 && iff_size >= 0xFFFFFFF4) { flac__utils_printf(stderr, 1, "%s: ERROR: stream is too big to fit in a single %s file\n", decoder_session->inbasefilename, fmt_desc); return false; } - if(decoder_session->is_wave_out) { - if(flac__utils_fwrite("RIFF", 1, 4, f) != 4) - return false; + if(is_wavish) { + if(format == FORMAT_RF64) { + if(flac__utils_fwrite("RF64", 1, 4, f) != 4) + return false; - if(!write_little_endian_uint32(f, foreign_metadata_size + aligned_data_size + (is_waveformatextensible?60:36))) /* filesize-8 */ - return false; + if(!write_little_endian_uint32(f, 0xffffffff)) + return false; + } + else { + if(flac__utils_fwrite("RIFF", 1, 4, f) != 4) + return false; + + if(!write_little_endian_uint32(f, (FLAC__uint32)iff_size)) /* filesize-8 */ + return false; + } if(flac__utils_fwrite("WAVE", 1, 4, f) != 4) return false; + if(format == FORMAT_RF64) { + if(flac__utils_fwrite("ds64", 1, 4, f) != 4) + return false; + + if(!write_little_endian_uint32(f, 28)) /* chunk size */ + return false; + + if(!write_little_endian_uint64(f, iff_size)) + return false; + + if(!write_little_endian_uint64(f, data_size)) + return false; + + if(!write_little_endian_uint64(f, samples)) /*@@@@@@ correct? */ + return false; + + if(!write_little_endian_uint32(f, 0)) /* table size */ + return false; + } + decoder_session->fm_offset1 = ftello(f); if(fm) { /* seek forward to {allocate} or {skip over already-written chunks} before "fmt " */ - for(i = 1; i < fm->format_block; i++) { + for(i = format==FORMAT_RF64?2:1; i < fm->format_block; i++) { if(fseeko(f, fm->blocks[i].size, SEEK_CUR) < 0) { flac__utils_printf(stderr, 1, "%s: ERROR: allocating/skipping foreign metadata before \"fmt \"\n", decoder_session->inbasefilename); return false; @@ -675,7 +637,7 @@ FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uin if(flac__utils_fwrite("data", 1, 4, f) != 4) return false; - if(!write_little_endian_uint32(f, (FLAC__uint32)data_size)) /* data size */ + if(!write_little_endian_uint32(f, format==FORMAT_RF64? 0xffffffff : (FLAC__uint32)data_size)) return false; decoder_session->fm_offset3 = ftello(f) + aligned_data_size; @@ -686,7 +648,7 @@ FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uin if(flac__utils_fwrite("FORM", 1, 4, f) != 4) return false; - if(!write_big_endian_uint32(f, foreign_metadata_size + aligned_data_size + 46 + ssnd_offset_size)) /* filesize-8 */ + if(!write_big_endian_uint32(f, (FLAC__uint32)iff_size)) /* filesize-8 */ return false; if(flac__utils_fwrite("AIFF", 1, 4, f) != 4) @@ -835,6 +797,19 @@ FLAC__bool write_little_endian_uint32(FILE *f, FLAC__uint32 val) return flac__utils_fwrite(b, 1, 4, f) == 4; } +FLAC__bool write_little_endian_uint64(FILE *f, FLAC__uint64 val) +{ + FLAC__byte *b = (FLAC__byte*)(&val); + if(is_big_endian_host_) { + FLAC__byte tmp; + tmp = b[7]; b[7] = b[0]; b[0] = tmp; + tmp = b[6]; b[6] = b[1]; b[1] = tmp; + tmp = b[5]; b[5] = b[2]; b[2] = tmp; + tmp = b[4]; b[4] = b[3]; b[3] = tmp; + } + return flac__utils_fwrite(b, 1, 8, f) == 8; +} + FLAC__bool write_big_endian_uint16(FILE *f, FLAC__uint16 val) { FLAC__byte *b = (FLAC__byte*)(&val); @@ -889,7 +864,7 @@ FLAC__bool write_sane_extended(FILE *f, unsigned val) FLAC__bool fixup_iff_headers(DecoderSession *d) { - const char *fmt_desc = (d->is_wave_out? "WAVE" : "AIFF"); + const char *fmt_desc = d->format==FORMAT_WAVE? "WAVE" : d->format==FORMAT_RF64? "RF64" : "AIFF"; FILE *f = fopen(d->outfilename, "r+b"); /* stream is positioned at beginning of file */ if(0 == f) { @@ -911,9 +886,17 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder DecoderSession *decoder_session = (DecoderSession*)client_data; FILE *fout = decoder_session->fout; const unsigned bps = frame->header.bits_per_sample, channels = frame->header.channels; - const unsigned shift = ((decoder_session->is_wave_out || decoder_session->is_aiff_out) && (bps%8)? 8-(bps%8): 0); - FLAC__bool is_big_endian = (decoder_session->is_aiff_out? true : (decoder_session->is_wave_out? false : decoder_session->is_big_endian)); - FLAC__bool is_unsigned_samples = (decoder_session->is_aiff_out? false : (decoder_session->is_wave_out? bps<=8 : decoder_session->is_unsigned_samples)); + const unsigned shift = (decoder_session->format != FORMAT_RAW && (bps%8)? 8-(bps%8): 0); + FLAC__bool is_big_endian = ( + decoder_session->format == FORMAT_AIFF || decoder_session->format == FORMAT_AIFF_C ? true : ( + decoder_session->format == FORMAT_WAVE || decoder_session->format == FORMAT_RF64 ? false : + decoder_session->is_big_endian + )); + FLAC__bool is_unsigned_samples = ( + decoder_session->format == FORMAT_AIFF || decoder_session->format == FORMAT_AIFF_C ? false : ( + decoder_session->format == FORMAT_WAVE || decoder_session->format == FORMAT_RF64 ? bps<=8 : + decoder_session->is_unsigned_samples + )); unsigned wide_samples = frame->header.blocksize, wide_sample, sample, channel, byte; unsigned frame_bytes = 0; static FLAC__int8 s8buffer[FLAC__MAX_BLOCK_SIZE * FLAC__MAX_CHANNELS * sizeof(FLAC__int32)]; /* WATCHOUT: can be up to 2 megs */ diff --git a/src/flac/decode.h b/src/flac/decode.h index 6ea6086..acb005c 100644 --- a/src/flac/decode.h +++ b/src/flac/decode.h @@ -51,24 +51,20 @@ typedef struct { FLAC__bool has_cue_specification; utils__CueSpecification cue_specification; FLAC__bool channel_map_none; /* --channel-map=none specified, eventually will expand to take actual channel map */ -} decode_options_t; - -/* used for AIFF also */ -typedef struct { - decode_options_t common; - foreign_metadata_t *foreign_metadata; /* NULL unless --keep-foreign-metadata requested */ -} wav_decode_options_t; -typedef struct { - decode_options_t common; - - FLAC__bool is_big_endian; - FLAC__bool is_unsigned_samples; -} raw_decode_options_t; + FileFormat format; + union { + struct { + FLAC__bool is_big_endian; + FLAC__bool is_unsigned_samples; + } raw; + struct { + foreign_metadata_t *foreign_metadata; /* NULL unless --keep-foreign-metadata requested */ + } iff; + } format_options; +} decode_options_t; /* outfile == 0 => test only */ -int flac__decode_aiff(const char *infilename, const char *outfilename, FLAC__bool analysis_mode, analysis_options aopts, wav_decode_options_t options); -int flac__decode_wav(const char *infilename, const char *outfilename, FLAC__bool analysis_mode, analysis_options aopts, wav_decode_options_t options); -int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool analysis_mode, analysis_options aopts, raw_decode_options_t options); +int flac__decode_file(const char *infilename, const char *outfilename, FLAC__bool analysis_mode, analysis_options aopts, decode_options_t options); #endif diff --git a/src/flac/encode.c b/src/flac/encode.c index c6f2c3f..ac3982b 100644 --- a/src/flac/encode.c +++ b/src/flac/encode.c @@ -54,6 +54,7 @@ #define max(x,y) ((x)>(y)?(x):(y)) /* this MUST be >= 588 so that sector aligning can take place with one read */ +/* this MUST be < 2^sizeof(size_t) / ( FLAC__MAX_CHANNELS * (FLAC__MAX_BITS_PER_SAMPLE/8) ) */ #define CHUNK_OF_SAMPLES 2048 typedef struct { @@ -103,7 +104,7 @@ typedef struct { FileFormat format; union { struct { - size_t data_bytes; + FLAC__uint64 data_bytes; FLAC__bool pad; } iff; struct { @@ -169,6 +170,7 @@ static void print_verify_error(EncoderSession *e); static FLAC__bool read_bytes(FILE *f, FLAC__byte *buf, size_t n, FLAC__bool eof_ok, const char *fn); static FLAC__bool read_uint16(FILE *f, FLAC__bool big_endian, FLAC__uint16 *val, const char *fn); static FLAC__bool read_uint32(FILE *f, FLAC__bool big_endian, FLAC__uint32 *val, const char *fn); +static FLAC__bool read_uint64(FILE *f, FLAC__bool big_endian, FLAC__uint64 *val, const char *fn); static FLAC__bool read_sane_extended(FILE *f, FLAC__uint32 *val, const char *fn); static FLAC__bool fskip_ahead(FILE *f, FLAC__uint64 offset); static unsigned count_channel_mask_bits(FLAC__uint32 mask); @@ -192,16 +194,16 @@ static FLAC__bool get_sample_info_raw(EncoderSession *e, encode_options_t option static FLAC__bool get_sample_info_wave(EncoderSession *e, encode_options_t options) { - FLAC__bool got_fmt_chunk = false, got_data_chunk = false; + FLAC__bool got_fmt_chunk = false, got_data_chunk = false, got_ds64_chunk = false; unsigned sample_rate = 0, channels = 0, bps = 0, shift = 0; FLAC__uint32 channel_mask = 0; - size_t data_bytes = 0; + FLAC__uint64 ds64_data_size = 0; e->info.is_unsigned_samples = false; e->info.is_big_endian = false; /* - * lookahead[] already has "RIFFxxxxWAVE", do "fmt" chunk and beginning of "data" chunk + * lookahead[] already has "RIFFxxxxWAVE" or "RF64xxxxWAVE", do chunks */ while(!feof(e->fin) && !got_data_chunk) { char chunk_id[5] = { '\0', '\0', '\0', '\0', '\0' }; /* one extra byte for terminating NUL so we can also treat it like a C string */ @@ -212,9 +214,52 @@ static FLAC__bool get_sample_info_wave(EncoderSession *e, encode_options_t optio if(feof(e->fin)) break; - if(!memcmp(chunk_id, "fmt ", 4)) { /* format chunk */ + if(options.format == FORMAT_RF64 && !memcmp(chunk_id, "ds64", 4)) { /* RF64 64-bit sizes chunk */ + FLAC__uint32 xx, data_bytes; + + if(got_ds64_chunk) { + flac__utils_printf(stderr, 1, "%s: ERROR: file has multiple 'ds64' chunks\n", e->inbasefilename); + return false; + } + if(got_fmt_chunk || got_data_chunk) { + flac__utils_printf(stderr, 1, "%s: ERROR: 'ds64' chunk appears after 'fmt ' or 'data' chunk\n", e->inbasefilename); + return false; + } + + /* ds64 chunk size */ + if(!read_uint32(e->fin, /*big_endian=*/false, &xx, e->inbasefilename)) + return false; + data_bytes = xx; + if(data_bytes < 28) { + flac__utils_printf(stderr, 1, "%s: ERROR: non-standard 'ds64' chunk has length = %u\n", e->inbasefilename, (unsigned)data_bytes); + return false; + } + if(data_bytes & 1) /* should never happen, but enforce WAVE alignment rules */ + data_bytes++; + + /* RIFF 64-bit size, lo/hi */ + if(!read_uint32(e->fin, /*big_endian=*/false, &xx, e->inbasefilename)) + return false; + if(!read_uint32(e->fin, /*big_endian=*/false, &xx, e->inbasefilename)) + return false; + + /* 'data' 64-bit size */ + if(!read_uint64(e->fin, /*big_endian=*/false, &ds64_data_size, e->inbasefilename)) + return false; + + data_bytes -= 16; + + /* skip any extra data in the ds64 chunk */ + if(!fskip_ahead(e->fin, data_bytes)) { + flac__utils_printf(stderr, 1, "%s: ERROR during read while skipping over extra 'ds64' data\n", e->inbasefilename); + return false; + } + + got_ds64_chunk = true; + } + else if(!memcmp(chunk_id, "fmt ", 4)) { /* format chunk */ FLAC__uint16 x; - FLAC__uint32 xx; + FLAC__uint32 xx, data_bytes; FLAC__uint16 wFormatTag; /* wFormatTag word from the 'fmt ' chunk */ unsigned block_align; @@ -262,6 +307,8 @@ static FLAC__bool get_sample_info_wave(EncoderSession *e, encode_options_t optio flac__utils_printf(stderr, 1, "%s: ERROR: non-standard 'fmt ' chunk has length = %u\n", e->inbasefilename, (unsigned)data_bytes); return false; } + if(data_bytes & 1) /* should never happen, but enforce WAVE alignment rules */ + data_bytes++; /* format code */ if(!read_uint16(e->fin, /*big_endian=*/false, &wFormatTag, e->inbasefilename)) @@ -462,6 +509,7 @@ static FLAC__bool get_sample_info_wave(EncoderSession *e, encode_options_t optio } else if(!memcmp(chunk_id, "data", 4)) { /* data chunk */ FLAC__uint32 xx; + FLAC__uint64 data_bytes; if(!got_fmt_chunk) { flac__utils_printf(stderr, 1, "%s: ERROR: got 'data' chunk before 'fmt' chunk\n", e->inbasefilename); @@ -471,21 +519,27 @@ static FLAC__bool get_sample_info_wave(EncoderSession *e, encode_options_t optio /* data size */ if(!read_uint32(e->fin, /*big_endian=*/false, &xx, e->inbasefilename)) return false; + data_bytes = xx; + if(options.format == FORMAT_RF64) { + if(!got_ds64_chunk) { + flac__utils_printf(stderr, 1, "%s: ERROR: RF64 file has no 'ds64' chunk before 'data' chunk\n", e->inbasefilename); + return false; + } + if(data_bytes == 0xffffffff) + data_bytes = ds64_data_size; + } if(options.ignore_chunk_sizes) { FLAC__ASSERT(!options.sector_align); - if(xx) { - flac__utils_printf(stderr, 1, "%s: WARNING: \"data\" chunk has non-zero size, using --ignore-chunk-sizes is probably a bad idea\n", e->inbasefilename, chunk_id); + if(data_bytes) { + flac__utils_printf(stderr, 1, "%s: WARNING: 'data' chunk has non-zero size, using --ignore-chunk-sizes is probably a bad idea\n", e->inbasefilename, chunk_id); if(e->treat_warnings_as_errors) return false; } - data_bytes = (size_t)0 - (size_t)e->info.bytes_per_wide_sample; /* max out data_bytes; we'll use EOF as signal to stop reading */ + data_bytes = (FLAC__uint64)0 - (FLAC__uint64)e->info.bytes_per_wide_sample; /* max out data_bytes; we'll use EOF as signal to stop reading */ } - else { - data_bytes = xx; - if(0 == data_bytes) { - flac__utils_printf(stderr, 1, "%s: ERROR: 'data' chunk has size of 0\n", e->inbasefilename); - return false; - } + else if(0 == data_bytes) { + flac__utils_printf(stderr, 1, "%s: ERROR: 'data' chunk has size of 0\n", e->inbasefilename); + return false; } e->fmt.iff.data_bytes = data_bytes; @@ -538,7 +592,8 @@ static FLAC__bool get_sample_info_wave(EncoderSession *e, encode_options_t optio static FLAC__bool get_sample_info_aiff(EncoderSession *e, encode_options_t options) { FLAC__bool got_comm_chunk = false, got_ssnd_chunk = false; - unsigned sample_rate = 0, channels = 0, bps = 0, shift = 0, sample_frames = 0; + unsigned sample_rate = 0, channels = 0, bps = 0, shift = 0; + FLAC__uint64 sample_frames = 0; FLAC__uint32 channel_mask = 0; e->info.is_unsigned_samples = false; @@ -587,7 +642,7 @@ static FLAC__bool get_sample_info_aiff(EncoderSession *e, encode_options_t optio return false; channels = (unsigned)x; if(channels > 2 && !options.channel_map_none) { - flac__utils_printf(stderr, 1, "%s: ERROR: unsupported number channels %u for AIFF\n", e->inbasefilename, channels); + flac__utils_printf(stderr, 1, "%s: ERROR: unsupported number of channels %u for AIFF\n", e->inbasefilename, channels); return false; } @@ -647,7 +702,7 @@ static FLAC__bool get_sample_info_aiff(EncoderSession *e, encode_options_t optio /* keep default channel order */ } else { - flac__utils_printf(stderr, 1, "%s: ERROR: unsupported number channels %u for AIFF\n", e->inbasefilename, channels); + flac__utils_printf(stderr, 1, "%s: ERROR: unsupported number of channels %u for AIFF\n", e->inbasefilename, channels); return false; } @@ -663,7 +718,8 @@ static FLAC__bool get_sample_info_aiff(EncoderSession *e, encode_options_t optio } else if(!memcmp(chunk_id, "SSND", 4) && !got_ssnd_chunk) { /* sound data chunk */ FLAC__uint32 xx; - unsigned offset = 0, block_size = 0, data_bytes; + FLAC__uint64 data_bytes; + unsigned offset = 0, block_size = 0; if(!got_comm_chunk) { flac__utils_printf(stderr, 1, "%s: ERROR: got 'SSND' chunk before 'COMM' chunk\n", e->inbasefilename); @@ -673,17 +729,21 @@ static FLAC__bool get_sample_info_aiff(EncoderSession *e, encode_options_t optio /* SSND chunk size */ if(!read_uint32(e->fin, /*big_endian=*/true, &xx, e->inbasefilename)) return false; + data_bytes = xx; if(options.ignore_chunk_sizes) { FLAC__ASSERT(!options.sector_align); - if(xx) { - flac__utils_printf(stderr, 1, "%s: WARNING: \"SSND\" chunk has non-zero size, using --ignore-chunk-sizes is probably a bad idea\n", e->inbasefilename, chunk_id); + if(data_bytes) { + flac__utils_printf(stderr, 1, "%s: WARNING: 'SSND' chunk has non-zero size, using --ignore-chunk-sizes is probably a bad idea\n", e->inbasefilename, chunk_id); if(e->treat_warnings_as_errors) return false; } - data_bytes = (size_t)0 - (size_t)e->info.bytes_per_wide_sample; /* max out data_bytes; we'll use EOF as signal to stop reading */ + data_bytes = (FLAC__uint64)0 - (FLAC__uint64)e->info.bytes_per_wide_sample; /* max out data_bytes; we'll use EOF as signal to stop reading */ + } + else if(data_bytes <= 8) { + flac__utils_printf(stderr, 1, "%s: ERROR: 'SSND' chunk has size <= 8\n", e->inbasefilename); + return false; } else { - data_bytes = xx; data_bytes -= 8; /* discount the offset and block size fields */ } e->fmt.iff.pad = (data_bytes & 1) ? true : false; @@ -832,13 +892,10 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co } /* read foreign metadata if requested */ - if( - (options.format == FORMAT_WAVE || options.format == FORMAT_AIFF || options.format == FORMAT_AIFF_C) && - options.format_options.iff.foreign_metadata - ) { + if(flac__utils_format_is_iff(options.format) && options.format_options.iff.foreign_metadata) { const char *error; if(!( - options.format == FORMAT_WAVE? + options.format == FORMAT_WAVE || options.format == FORMAT_RF64? flac__foreign_metadata_read_from_wave(options.format_options.iff.foreign_metadata, infilename, &error) : flac__foreign_metadata_read_from_aiff(options.format_options.iff.foreign_metadata, infilename, &error) )) { @@ -854,6 +911,7 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co return EncoderSession_finish_error(&encoder_session); break; case FORMAT_WAVE: + case FORMAT_RF64: if(!get_sample_info_wave(&encoder_session, options)) return EncoderSession_finish_error(&encoder_session); break; @@ -922,6 +980,7 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co total_samples_in_input = (FLAC__uint64)infilesize / encoder_session.info.bytes_per_wide_sample + *options.align_reservoir_samples; break; case FORMAT_WAVE: + case FORMAT_RF64: case FORMAT_AIFF: case FORMAT_AIFF_C: total_samples_in_input = encoder_session.fmt.iff.data_bytes / encoder_session.info.bytes_per_wide_sample + *options.align_reservoir_samples; @@ -963,9 +1022,10 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co encoder_session.total_samples_to_encode = total_samples_in_input - skip; break; case FORMAT_WAVE: + case FORMAT_RF64: case FORMAT_AIFF: case FORMAT_AIFF_C: - encoder_session.fmt.iff.data_bytes -= (unsigned)skip * encoder_session.info.bytes_per_wide_sample; /*@@@ WATCHOUT: 4GB limit */ + encoder_session.fmt.iff.data_bytes -= skip * encoder_session.info.bytes_per_wide_sample; if(options.ignore_chunk_sizes) { encoder_session.total_samples_to_encode = 0; flac__utils_printf(stderr, 2, "(No runtime statistics possible; please wait for encoding to finish...)\n"); @@ -990,8 +1050,8 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co FLAC__ASSERT(!options.sector_align); if(options.format == FORMAT_RAW) infilesize -= (off_t)trim * encoder_session.info.bytes_per_wide_sample; - else if(options.format == FORMAT_WAVE || options.format == FORMAT_AIFF || options.format == FORMAT_AIFF_C) - encoder_session.fmt.iff.data_bytes -= (unsigned)trim * encoder_session.info.bytes_per_wide_sample; + else if(flac__utils_format_is_iff(options.format)) + encoder_session.fmt.iff.data_bytes -= trim * encoder_session.info.bytes_per_wide_sample; encoder_session.total_samples_to_encode -= trim; } if(options.sector_align && (options.format != FORMAT_RAW || infilesize >=0)) { /* for RAW, need to know the filesize */ @@ -1007,12 +1067,16 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co encoder_session.unencoded_size = encoder_session.total_samples_to_encode * encoder_session.info.bytes_per_wide_sample; break; case FORMAT_WAVE: - /* +44 for the size of the WAV headers; this is just an estimate for the progress indicator and doesn't need to be exact */ + /* +44 for the size of the WAVE headers; this is just an estimate for the progress indicator and doesn't need to be exact */ encoder_session.unencoded_size = encoder_session.total_samples_to_encode * encoder_session.info.bytes_per_wide_sample + 44; break; + case FORMAT_RF64: + /* +72 for the size of the RF64 headers; this is just an estimate for the progress indicator and doesn't need to be exact */ + encoder_session.unencoded_size = encoder_session.total_samples_to_encode * encoder_session.info.bytes_per_wide_sample + 80; + break; case FORMAT_AIFF: case FORMAT_AIFF_C: - /* +54 for the size of the WAV headers; this is just an estimate for the progress indicator and doesn't need to be exact */ + /* +54 for the size of the AIFF headers; this is just an estimate for the progress indicator and doesn't need to be exact */ encoder_session.unencoded_size = encoder_session.total_samples_to_encode * encoder_session.info.bytes_per_wide_sample + 54; break; case FORMAT_FLAC: @@ -1064,6 +1128,7 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co } break; case FORMAT_WAVE: + case FORMAT_RF64: case FORMAT_AIFF: case FORMAT_AIFF_C: if(!fskip_ahead(encoder_session.fin, skip * encoder_session.info.bytes_per_wide_sample)) { @@ -1117,7 +1182,7 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co infilesize -= (off_t)((*options.align_reservoir_samples) * encoder_session.info.bytes_per_wide_sample); FLAC__ASSERT(infilesize >= 0); } - else if(options.format == FORMAT_WAVE || options.format == FORMAT_AIFF || options.format == FORMAT_AIFF_C) + else if(flac__utils_format_is_iff(options.format)) encoder_session.fmt.iff.data_bytes -= (*options.align_reservoir_samples) * encoder_session.info.bytes_per_wide_sample; } } @@ -1225,10 +1290,15 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co } break; case FORMAT_WAVE: + case FORMAT_RF64: case FORMAT_AIFF: case FORMAT_AIFF_C: while(encoder_session.fmt.iff.data_bytes > 0) { - size_t bytes_read = fread(ucbuffer_, sizeof(unsigned char), min(encoder_session.fmt.iff.data_bytes, CHUNK_OF_SAMPLES * encoder_session.info.bytes_per_wide_sample), infile); + const size_t bytes_to_read = (size_t)min( + encoder_session.fmt.iff.data_bytes, + (FLAC__uint64)CHUNK_OF_SAMPLES * (FLAC__uint64)encoder_session.info.bytes_per_wide_sample + ); + size_t bytes_read = fread(ucbuffer_, sizeof(unsigned char), bytes_to_read, infile); if(bytes_read == 0) { if(ferror(infile)) { flac__utils_printf(stderr, 1, "%s: ERROR during read\n", encoder_session.inbasefilename); @@ -1337,7 +1407,7 @@ int flac__encode_file(FILE *infile, off_t infilesize, const char *infilename, co &encoder_session, info_align_carry, info_align_zero, - (options.format == FORMAT_WAVE || options.format == FORMAT_AIFF || options.format == FORMAT_AIFF_C)? options.format_options.iff.foreign_metadata : 0 + flac__utils_format_is_iff(options.format)? options.format_options.iff.foreign_metadata : 0 ); } @@ -1388,6 +1458,7 @@ FLAC__bool EncoderSession_construct(EncoderSession *e, encode_options_t options, case FORMAT_RAW: break; case FORMAT_WAVE: + case FORMAT_RF64: case FORMAT_AIFF: case FORMAT_AIFF_C: e->fmt.iff.data_bytes = 0; @@ -1842,7 +1913,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio * we're not encoding from FLAC so we will build the metadata * from scratch */ - const foreign_metadata_t *foreign_metadata = (options.format == FORMAT_WAVE || options.format == FORMAT_AIFF || options.format == FORMAT_AIFF_C)? options.format_options.iff.foreign_metadata : 0; + const foreign_metadata_t *foreign_metadata = flac__utils_format_is_iff(options.format)? options.format_options.iff.foreign_metadata : 0; if(e->seek_table_template->data.seek_table.num_points > 0) { e->seek_table_template->is_last = false; /* the encoder will set this for us */ @@ -1870,7 +1941,6 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio } static_metadata_append(&static_metadata, p, /*needs_delete=*/true); static_metadata.metadata[static_metadata.num_metadata-1]->length = FLAC__STREAM_METADATA_APPLICATION_ID_LEN/8 + foreign_metadata->blocks[i].size; -/*fprintf(stderr,"@@@@@@ add PADDING=%u\n",static_metadata.metadata[static_metadata.num_metadata-1]->length);*/ } } if(options.padding != 0) { @@ -2592,6 +2662,20 @@ FLAC__bool read_uint32(FILE *f, FLAC__bool big_endian, FLAC__uint32 *val, const return true; } +FLAC__bool read_uint64(FILE *f, FLAC__bool big_endian, FLAC__uint64 *val, const char *fn) +{ + if(!read_bytes(f, (FLAC__byte*)val, 8, /*eof_ok=*/false, fn)) + return false; + if(is_big_endian_host_ != big_endian) { + FLAC__byte tmp, *b = (FLAC__byte*)val; + tmp = b[7]; b[7] = b[0]; b[0] = tmp; + tmp = b[6]; b[6] = b[1]; b[1] = tmp; + tmp = b[5]; b[5] = b[2]; b[2] = tmp; + tmp = b[4]; b[4] = b[3]; b[3] = tmp; + } + return true; +} + FLAC__bool read_sane_extended(FILE *f, FLAC__uint32 *val, const char *fn) /* Read an IEEE 754 80-bit (aka SANE) extended floating point value from 'f', * convert it into an integral value and store in 'val'. Return false if only diff --git a/src/flac/foreign_metadata.c b/src/flac/foreign_metadata.c index 6a64620..424ad3d 100644 --- a/src/flac/foreign_metadata.c +++ b/src/flac/foreign_metadata.c @@ -53,9 +53,15 @@ static FLAC__uint32 unpack32le_(const FLAC__byte *b) return (FLAC__uint32)b[0] + ((FLAC__uint32)b[1]<<8) + ((FLAC__uint32)b[2]<<16) + ((FLAC__uint32)b[3]<<24); } +static FLAC__uint64 unpack64le_(const FLAC__byte *b) +{ + return (FLAC__uint64)b[0] + ((FLAC__uint64)b[1]<<8) + ((FLAC__uint64)b[2]<<16) + ((FLAC__uint64)b[3]<<24) + ((FLAC__uint64)b[4]<<32) + ((FLAC__uint64)b[5]<<40) + ((FLAC__uint64)b[6]<<48) + ((FLAC__uint64)b[7]<<56); +} + +/* copies 'size' bytes from file 'fin' to 'fout', filling in *error with 'read_error' or 'write_error' as necessary */ static FLAC__bool copy_data_(FILE *fin, FILE *fout, size_t size, const char **error, const char * const read_error, const char * const write_error) { - static FLAC__byte buffer[4096]; + FLAC__byte buffer[4096]; size_t left; for(left = size; left > 0; ) { size_t need = min(sizeof(buffer), left); @@ -100,7 +106,7 @@ static FLAC__bool read_from_aiff_(foreign_metadata_t *fm, FILE *f, const char ** } if(!append_block_(fm, offset, 12, error)) return false; - eof_offset = 8 + unpack32be_(buffer+4); + eof_offset = (off_t)8 + (off_t)unpack32be_(buffer+4); while(!feof(f)) { FLAC__uint32 size; if((offset = ftello(f)) < 0) { @@ -151,6 +157,7 @@ static FLAC__bool read_from_aiff_(foreign_metadata_t *fm, FILE *f, const char ** } if(!append_block_(fm, offset, 8 + (memcmp(buffer, "SSND", 4)? size : 8 + fm->ssnd_offset_size), error)) return false; + /* skip to next chunk */ if(fseeko(f, size, SEEK_CUR) < 0) { if(error) *error = "invalid AIFF file: seek error (011)"; return false; @@ -174,18 +181,25 @@ static FLAC__bool read_from_aiff_(foreign_metadata_t *fm, FILE *f, const char ** static FLAC__bool read_from_wave_(foreign_metadata_t *fm, FILE *f, const char **error) { FLAC__byte buffer[12]; - off_t offset, eof_offset; + off_t offset, eof_offset = -1, ds64_data_size = -1; if((offset = ftello(f)) < 0) { if(error) *error = "ftello() error (001)"; return false; } - if(fread(buffer, 1, 12, f) < 12 || memcmp(buffer, "RIFF", 4) || memcmp(buffer+8, "WAVE", 4)) { + if(fread(buffer, 1, 12, f) < 12 || (memcmp(buffer, "RIFF", 4) && memcmp(buffer, "RF64", 4)) || memcmp(buffer+8, "WAVE", 4)) { if(error) *error = "unsupported RIFF layout (002)"; return false; } + if(!memcmp(buffer, "RF64", 4)) + fm->is_rf64 = true; + if(fm->is_rf64 && sizeof(off_t) < 8) { + if(error) *error = "RF64 is not supported on this compile (r00)"; + return false; + } if(!append_block_(fm, offset, 12, error)) return false; - eof_offset = 8 + unpack32le_(buffer+4); + if(!fm->is_rf64 || unpack32le_(buffer+4) != 0xffffffffu) + eof_offset = (off_t)8 + (off_t)unpack32le_(buffer+4); while(!feof(f)) { FLAC__uint32 size; if((offset = ftello(f)) < 0) { @@ -223,14 +237,77 @@ static FLAC__bool read_from_wave_(foreign_metadata_t *fm, FILE *f, const char ** return false; } fm->audio_block = fm->num_blocks; + if(fm->is_rf64 && fm->num_blocks < 2) { + if(error) *error = "invalid RF64 file: \"data\" chunk before \"ds64\" chunk (r01)"; + return false; + } } if(!append_block_(fm, offset, 8 + (memcmp(buffer, "data", 4)? size : 0), error)) return false; - if(fseeko(f, size, SEEK_CUR) < 0) { - if(error) *error = "invalid WAVE file: seek error (009)"; - return false; + /* parse ds64 chunk if necessary */ + if(fm->is_rf64 && fm->num_blocks == 2) { + FLAC__byte buffer2[7*4]; + if(memcmp(buffer, "ds64", 4)) { + if(error) *error = "invalid RF64 file: \"ds64\" chunk does not immediately follow \"WAVE\" marker (r02)"; + return false; + } + /* unpack the size again since we don't want the padding byte effect */ + size = unpack32le_(buffer+4); + if(size < sizeof(buffer2)) { + if(error) *error = "invalid RF64 file: \"ds64\" chunk size is < 28 (r03)"; + return false; + } + if(size > sizeof(buffer2)) { + if(error) *error = "RF64 file has \"ds64\" chunk with extra size table, which is not currently supported (r04)"; + return false; + } + if(fread(buffer2, 1, sizeof(buffer2), f) < sizeof(buffer2)) { + if(error) *error = "unexpected EOF reading \"ds64\" chunk data in RF64 file (r05)"; + return false; + } + ds64_data_size = (off_t)unpack64le_(buffer2+8); + if(ds64_data_size == (off_t)(-1)) { + if(error) *error = "RF64 file has \"ds64\" chunk with data size == -1 (r08)"; + return false; + } + /* check if pad byte needed */ + if(ds64_data_size & 1) + ds64_data_size++; + /* @@@ [2^63 limit] */ + if(ds64_data_size < 0) { + if(error) *error = "RF64 file too large (r09)"; + return false; + } + if(unpack32le_(buffer2+24)) { + if(error) *error = "RF64 file has \"ds64\" chunk with extra size table, which is not currently supported (r06)"; + return false; + } + eof_offset = (off_t)8 + (off_t)unpack64le_(buffer2); + /* @@@ [2^63 limit] */ + if((off_t)unpack64le_(buffer2) < 0 || eof_offset < 0) { + if(error) *error = "RF64 file too large (r07)"; + return false; + } + } + else { /* skip to next chunk */ + if(fm->is_rf64 && !memcmp(buffer, "data", 4) && unpack32le_(buffer+4) == 0xffffffffu) { + if(fseeko(f, ds64_data_size, SEEK_CUR) < 0) { + if(error) *error = "invalid RF64 file: seek error (r10)"; + return false; + } + } + else { + if(fseeko(f, size, SEEK_CUR) < 0) { + if(error) *error = "invalid WAVE file: seek error (009)"; + return false; + } + } } } + if(fm->is_rf64 && eof_offset == (off_t)(-1)) { + if(error) *error = "invalid RF64 file: all RIFF sizes are -1 (r11)"; + return false; + } if(eof_offset != ftello(f)) { if(error) *error = "invalid WAVE file: unexpected EOF (010)"; return false; @@ -306,7 +383,7 @@ static FLAC__bool read_from_flac_(foreign_metadata_t *fm, FILE *f, FLAC__Metadat { FLAC__byte id[4], buffer[12]; off_t offset; - FLAC__bool type_found = false; + FLAC__bool type_found = false, ds64_found = false; FLAC__ASSERT(FLAC__STREAM_METADATA_APPLICATION_ID_LEN == sizeof(id)*8); @@ -332,8 +409,9 @@ static FLAC__bool read_from_flac_(foreign_metadata_t *fm, FILE *f, FLAC__Metadat if(error) *error = "read error (005)"; return false; } - if(fm->num_blocks == 0) { - if(fm->type == FOREIGN_BLOCK_TYPE__RIFF && 0 == memcmp(buffer, "RIFF", 4)) + if(fm->num_blocks == 0) { /* first block? */ + fm->is_rf64 = 0 == memcmp(buffer, "RF64", 4); + if(fm->type == FOREIGN_BLOCK_TYPE__RIFF && (0 == memcmp(buffer, "RIFF", 4) || fm->is_rf64)) type_found = true; else if(fm->type == FOREIGN_BLOCK_TYPE__AIFF && 0 == memcmp(buffer, "FORM", 4)) type_found = true; @@ -371,32 +449,39 @@ static FLAC__bool read_from_flac_(foreign_metadata_t *fm, FILE *f, FLAC__Metadat } fm->audio_block = fm->num_blocks; } + else if(fm->is_rf64 && fm->num_blocks == 1) { + if(memcmp(buffer, "ds64", 4)) { + if(error) *error = "invalid RF64 metadata: second chunk is not \"ds64\" (011)"; + return false; + } + ds64_found = true; + } } else if(fm->type == FOREIGN_BLOCK_TYPE__AIFF) { if(!memcmp(buffer, "COMM", 4)) { if(fm->format_block) { - if(error) *error = "invalid AIFF metadata: multiple \"COMM\" chunks (011)"; + if(error) *error = "invalid AIFF metadata: multiple \"COMM\" chunks (012)"; return false; } if(fm->audio_block) { - if(error) *error = "invalid AIFF metadata: \"SSND\" chunk before \"COMM\" chunk (012)"; + if(error) *error = "invalid AIFF metadata: \"SSND\" chunk before \"COMM\" chunk (013)"; return false; } fm->format_block = fm->num_blocks; } else if(!memcmp(buffer, "SSND", 4)) { if(fm->audio_block) { - if(error) *error = "invalid AIFF metadata: multiple \"SSND\" chunks (013)"; + if(error) *error = "invalid AIFF metadata: multiple \"SSND\" chunks (014)"; return false; } if(!fm->format_block) { - if(error) *error = "invalid AIFF metadata: \"SSND\" chunk before \"COMM\" chunk (014)"; + if(error) *error = "invalid AIFF metadata: \"SSND\" chunk before \"COMM\" chunk (015)"; return false; } fm->audio_block = fm->num_blocks; /* read SSND offset size */ if(fread(buffer+4, 1, 8, f) != 8) { - if(error) *error = "read error (015)"; + if(error) *error = "read error (016)"; return false; } fm->ssnd_offset_size = unpack32be_(buffer+8); @@ -405,22 +490,26 @@ static FLAC__bool read_from_flac_(foreign_metadata_t *fm, FILE *f, FLAC__Metadat else { FLAC__ASSERT(0); /* double protection: */ - if(error) *error = "unsupported foreign metadata found, may need newer FLAC decoder (016)"; + if(error) *error = "unsupported foreign metadata found, may need newer FLAC decoder (017)"; return false; } if(!append_block_(fm, offset, FLAC__metadata_simple_iterator_get_block_length(it)-sizeof(id), error)) return false; } if(!type_found) { - if(error) *error = "no foreign metadata found (017)"; + if(error) *error = "no foreign metadata found (018)"; + return false; + } + if(fm->is_rf64 && !ds64_found) { + if(error) *error = "invalid RF64 file: second chunk is not \"ds64\" (019)"; return false; } if(!fm->format_block) { - if(error) *error = fm->type==FOREIGN_BLOCK_TYPE__RIFF? "invalid WAVE file: missing \"fmt \" chunk (018)" : "invalid AIFF file: missing \"COMM\" chunk (018)"; + if(error) *error = fm->type==FOREIGN_BLOCK_TYPE__RIFF? "invalid WAVE file: missing \"fmt \" chunk (020)" : "invalid AIFF file: missing \"COMM\" chunk (021)"; return false; } if(!fm->audio_block) { - if(error) *error = fm->type==FOREIGN_BLOCK_TYPE__RIFF? "invalid WAVE file: missing \"data\" chunk (019)" : "invalid AIFF file: missing \"SSND\" chunk (019)"; + if(error) *error = fm->type==FOREIGN_BLOCK_TYPE__RIFF? "invalid WAVE file: missing \"data\" chunk (020)" : "invalid AIFF file: missing \"SSND\" chunk (023)"; return false; } return true; @@ -433,7 +522,8 @@ static FLAC__bool write_to_iff_(foreign_metadata_t *fm, FILE *fin, FILE *fout, o if(error) *error = "seek failed in WAVE/AIFF file (002)"; return false; } - for(i = 1; i < fm->format_block; i++) { + /* don't write first (RIFF/RF64/FORM) chunk, or ds64 chunk in the case of RF64 */ + for(i = fm->is_rf64?2:1; i < fm->format_block; i++) { if(fseeko(fin, fm->blocks[i].offset, SEEK_SET) < 0) { if(error) *error = "seek failed in FLAC file (003)"; return false; @@ -453,6 +543,7 @@ static FLAC__bool write_to_iff_(foreign_metadata_t *fm, FILE *fin, FILE *fout, o if(!copy_data_(fin, fout, fm->blocks[i].size, error, "read failed in WAVE/AIFF file (008)", "write failed in FLAC file (009)")) return false; } + /*@@@@@@ need to write SSND offset bytes here? decoder left them as zeroes */ if(fseeko(fout, offset3, SEEK_SET) < 0) { if(error) *error = "seek failed in WAVE/AIFF file (010)"; return false; @@ -470,9 +561,12 @@ static FLAC__bool write_to_iff_(foreign_metadata_t *fm, FILE *fin, FILE *fout, o foreign_metadata_t *flac__foreign_metadata_new(foreign_block_type_t type) { + /* calloc() to zero all the member variables */ foreign_metadata_t *x = (foreign_metadata_t*)calloc(sizeof(foreign_metadata_t), 1); - if(x) + if(x) { x->type = type; + x->is_rf64 = false; + } return x; } diff --git a/src/flac/foreign_metadata.h b/src/flac/foreign_metadata.h index 14be438..11c1099 100644 --- a/src/flac/foreign_metadata.h +++ b/src/flac/foreign_metadata.h @@ -27,21 +27,30 @@ #include "utils.h" /* WATCHOUT: these enums are used to index internal arrays */ -typedef enum { FOREIGN_BLOCK_TYPE__AIFF = 0, FOREIGN_BLOCK_TYPE__RIFF = 1 } foreign_block_type_t; +typedef enum { + FOREIGN_BLOCK_TYPE__AIFF = 0, /* for AIFF and AIFF-C */ + FOREIGN_BLOCK_TYPE__RIFF = 1 /* for WAVE and RF64 */ +} foreign_block_type_t; typedef struct { /* for encoding, this will be the offset in the WAVE/AIFF file of the chunk */ /* for decoding, this will be the offset in the FLAC file of the chunk data inside the APPLICATION block */ off_t offset; + /* size is the actual size in bytes of the chunk to be stored/recreated. */ + /* It includes the 8 bytes of chunk type and size, and any padding byte for alignment. */ + /* For 'data'/'SSND' chunks, the size does not include the actual sound or padding bytes */ + /* because these are not stored, they are recreated from the compressed FLAC stream. */ + /* So for RIFF 'data', size is 8, and for AIFF 'SSND', size is 8 + 8 + ssnd_offset_size */ FLAC__uint32 size; } foreign_block_t; typedef struct { - foreign_block_type_t type; /* currently we don't support multiple foreign types in a stream (an maybe never will) */ + foreign_block_type_t type; /* currently we don't support multiple foreign types in a stream (and maybe never will) */ foreign_block_t *blocks; size_t num_blocks; size_t format_block; /* block number of 'fmt ' or 'COMM' chunk */ size_t audio_block; /* block number of 'data' or 'SSND' chunk */ + FLAC__bool is_rf64; /* always false if type!=RIFF */ FLAC__uint32 ssnd_offset_size; /* 0 if type!=AIFF */ } foreign_metadata_t; diff --git a/src/flac/iffscan.c b/src/flac/iffscan.c index 8a5f31f..64e9638 100644 --- a/src/flac/iffscan.c +++ b/src/flac/iffscan.c @@ -42,6 +42,11 @@ static FLAC__uint32 unpack32le_(const FLAC__byte *b) return (FLAC__uint32)b[0] + ((FLAC__uint32)b[1]<<8) + ((FLAC__uint32)b[2]<<16) + ((FLAC__uint32)b[3]<<24); } +static FLAC__uint64 unpack64le_(const FLAC__byte *b) +{ + return (FLAC__uint64)b[0] + ((FLAC__uint64)b[1]<<8) + ((FLAC__uint64)b[2]<<16) + ((FLAC__uint64)b[3]<<24) + ((FLAC__uint64)b[4]<<32) + ((FLAC__uint64)b[5]<<40) + ((FLAC__uint64)b[6]<<48) + ((FLAC__uint64)b[7]<<56); +} + static FLAC__uint32 unpack32_(const FLAC__byte *b, foreign_block_type_t type) { if(type == FOREIGN_BLOCK_TYPE__AIFF) @@ -53,7 +58,7 @@ static FLAC__uint32 unpack32_(const FLAC__byte *b, foreign_block_type_t type) int main(int argc, char *argv[]) { FILE *f; - char buf[12]; + char buf[36]; foreign_metadata_t *fm; const char *fn, *error; size_t i; @@ -69,7 +74,7 @@ int main(int argc, char *argv[]) return 1; } fclose(f); - if(0 == (fm = flac__foreign_metadata_new(memcmp(buf, "RIFF", 4)? FOREIGN_BLOCK_TYPE__AIFF : FOREIGN_BLOCK_TYPE__RIFF))) { + if(0 == (fm = flac__foreign_metadata_new(memcmp(buf, "RIFF", 4) && memcmp(buf, "RF64", 4)? FOREIGN_BLOCK_TYPE__AIFF : FOREIGN_BLOCK_TYPE__RIFF))) { fprintf(stderr, "ERROR: out of memory\n"); return 1; } @@ -94,7 +99,7 @@ int main(int argc, char *argv[]) fprintf(stderr, "ERROR seeking in %s\n", fn); return 1; } - if(fread(buf, 1, 12, f) != 12) { + if(fread(buf, 1, i==0?12:8, f) != (i==0?12:8)) { fprintf(stderr, "ERROR reading %s\n", fn); return 1; } @@ -104,6 +109,22 @@ int main(int argc, char *argv[]) printf(" type:[%c%c%c%c]", buf[8], buf[9], buf[10], buf[11]); else if(fm->type == FOREIGN_BLOCK_TYPE__AIFF && i == fm->audio_block) printf(" offset size=%08x=(%10u)", fm->ssnd_offset_size, fm->ssnd_offset_size); + else if(fm->type == FOREIGN_BLOCK_TYPE__RIFF && i == 1 && !memcmp(buf, "ds64", 4)) { + if(fread(buf+8, 1, 36-8, f) != 36-8) { + fprintf(stderr, "ERROR reading %s\n", fn); + return 1; + } +#ifdef _MSC_VER + printf(" RIFF size=%016I64x=(%I64u)", unpack64le_(buf+8), unpack64le_(buf+8)); + printf(" data size=%016I64x=(%I64u)", unpack64le_(buf+16), unpack64le_(buf+16)); + printf(" sample count=%016I64x=(%I64u)", unpack64le_(buf+24), unpack64le_(buf+24)); +#else + printf(" RIFF size=%016llx=(%llu)", unpack64le_(buf+8), unpack64le_(buf+8)); + printf(" data size=%016llx=(%llu)", unpack64le_(buf+16), unpack64le_(buf+16)); + printf(" sample count=%016llx=(%llu)", unpack64le_(buf+24), unpack64le_(buf+24)); +#endif + printf(" table size=%08x=(%u)", unpack32le_(buf+32), unpack32le_(buf+32)); + } printf("\n"); } fclose(f); diff --git a/src/flac/main.c b/src/flac/main.c index ca8cad9..6f29d01 100644 --- a/src/flac/main.c +++ b/src/flac/main.c @@ -147,6 +147,7 @@ static struct share__option long_options_[] = { { "fast" , share__no_argument, 0, '0' }, { "verify" , share__no_argument, 0, 'V' }, { "force-aiff-format" , share__no_argument, 0, 0 }, + { "force-rf64-format" , share__no_argument, 0, 0 }, { "force-raw-format" , share__no_argument, 0, 0 }, { "lax" , share__no_argument, 0, 0 }, { "replay-gain" , share__no_argument, 0, 0 }, @@ -241,6 +242,7 @@ static struct { long serial_number; /* this is the Ogg serial number and is unused for native FLAC */ FLAC__bool force_to_stdout; FLAC__bool force_aiff_format; + FLAC__bool force_rf64_format; FLAC__bool force_raw_format; FLAC__bool delete_input; FLAC__bool preserve_modtime; @@ -378,8 +380,8 @@ int do_it(void) if(!FLAC__format_sample_rate_is_valid(option_values.format_sample_rate)) return usage_error("ERROR: invalid sample rate '%u', must be > 0 and <= %u\n", option_values.format_sample_rate, FLAC__MAX_SAMPLE_RATE); } - if(option_values.force_raw_format && option_values.force_aiff_format) - return usage_error("ERROR: only one of --force-raw-format and --force-aiff-format allowed\n"); + if((option_values.force_raw_format?1:0) + (option_values.force_aiff_format?1:0) + (option_values.force_rf64_format?1:0) > 1) + return usage_error("ERROR: only one of --force-raw-format/--force-aiff-format/--force-rf64-format allowed\n"); if(option_values.mode_decode) { if(!option_values.force_raw_format) { if(option_values.format_is_big_endian >= 0) @@ -563,6 +565,7 @@ FLAC__bool init_options(void) option_values.serial_number = 0; option_values.force_to_stdout = false; option_values.force_aiff_format = false; + option_values.force_rf64_format = false; option_values.force_raw_format = false; option_values.delete_input = false; option_values.preserve_modtime = true; @@ -760,6 +763,9 @@ int parse_option(int short_option, const char *long_option, const char *option_a else if(0 == strcmp(long_option, "force-aiff-format")) { option_values.force_aiff_format = true; } + else if(0 == strcmp(long_option, "force-rf64-format")) { + option_values.force_rf64_format = true; + } else if(0 == strcmp(long_option, "force-raw-format")) { option_values.force_raw_format = true; } @@ -1272,6 +1278,7 @@ void show_help(void) printf(" --sign={signed|unsigned} Sign of samples\n"); printf(" --input-size=# Size of the raw input in bytes\n"); printf(" --force-aiff-format Force decoding to AIFF format\n"); + printf(" --force-rf64-format Force decoding to RF64 format\n"); printf(" --force-raw-format Treat input or output as raw samples\n"); printf("negative options:\n"); printf(" --no-adaptive-mid-side\n"); @@ -1306,8 +1313,8 @@ void show_explain(void) usage_header(); usage_summary(); printf("For encoding:\n"); - printf(" The input file(s) may be a PCM WAVE file, AIFF (or uncompressed AIFF-C)\n"); - printf(" file, or raw samples.\n"); + printf(" The input file(s) may be a PCM WAVE or RF64 file, AIFF (or uncompressed\n"); + printf(" AIFF-C) file, or raw samples.\n"); printf(" The output file(s) will be in native FLAC or Ogg FLAC format\n"); printf("For decoding, the reverse is true.\n"); printf("\n"); @@ -1607,6 +1614,11 @@ void show_explain(void) printf(" set by -o) ends with .aif or .aiff; this option\n"); printf(" has no effect when encoding since input AIFF is\n"); printf(" auto-detected.\n"); + printf(" --force-rf64-format Force the decoder to output RF64 format. This\n"); + printf(" option is not needed if the output filename (as\n"); + printf(" set by -o) ends with .rf64; this option\n"); + printf(" has no effect when encoding since input RF64 is\n"); + printf(" auto-detected.\n"); printf(" --force-raw-format Force input (when encoding) or output (when\n"); printf(" decoding) to be treated as raw samples\n"); printf("negative options:\n"); @@ -1639,7 +1651,7 @@ void show_explain(void) void format_mistake(const char *infilename, FileFormat wrong, FileFormat right) { /* WATCHOUT: indexed by FileFormat */ - static const char * const ff[] = { " raw", " WAVE", "n AIFF", "n AIFF-C", " FLAC", "n Ogg FLAC" }; + static const char * const ff[] = { " raw", " WAVE", "n RF64", "n AIFF", "n AIFF-C", " FLAC", "n Ogg FLAC" }; flac__utils_printf(stderr, 1, "WARNING: %s is not a%s file; treating as a%s file\n", infilename, ff[wrong], ff[right]); } @@ -1677,6 +1689,8 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ /* first set format based on name */ if(strlen(infilename) >= 4 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-4), ".wav")) input_format = FORMAT_WAVE; + if(strlen(infilename) >= 4 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-4), ".rf64")) + input_format = FORMAT_RF64; else if(strlen(infilename) >= 4 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-4), ".aif")) input_format = FORMAT_AIFF; else if(strlen(infilename) >= 5 && 0 == FLAC__STRCASECMP(infilename+(strlen(infilename)-5), ".aiff")) @@ -1690,6 +1704,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ /* attempt to guess the file type based on the first 12 bytes */ if((lookahead_length = fread(lookahead, 1, 12, encode_infile)) < 12) { + /* all supported non-raw formats have at least 12 bytes of header to read */ if(input_format != FORMAT_RAW) { format_mistake(infilename, input_format, FORMAT_RAW); if(option_values.treat_warnings_as_errors) { @@ -1697,25 +1712,29 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ return 1; } } + /* force to raw */ input_format = FORMAT_RAW; } else { - if(!strncmp((const char *)lookahead, "ID3", 3)) { + if(!memcmp(lookahead, "ID3", 3)) { flac__utils_printf(stderr, 1, "ERROR: input file %s has an ID3v2 tag\n", infilename); return 1; } - else if(!strncmp((const char *)lookahead, "RIFF", 4) && !strncmp((const char *)lookahead+8, "WAVE", 4)) + else if(!memcmp(lookahead, "RIFF", 4) && !memcmp(lookahead+8, "WAVE", 4)) input_format = FORMAT_WAVE; - else if(!strncmp((const char *)lookahead, "FORM", 4) && !strncmp((const char *)lookahead+8, "AIFF", 4)) + else if(!memcmp(lookahead, "RF64", 4) && !memcmp(lookahead+8, "WAVE", 4)) + input_format = FORMAT_RF64; + else if(!memcmp(lookahead, "FORM", 4) && !memcmp(lookahead+8, "AIFF", 4)) input_format = FORMAT_AIFF; - else if(!strncmp((const char *)lookahead, "FORM", 4) && !strncmp((const char *)lookahead+8, "AIFC", 4)) + else if(!memcmp(lookahead, "FORM", 4) && !memcmp(lookahead+8, "AIFC", 4)) input_format = FORMAT_AIFF_C; else if(!memcmp(lookahead, FLAC__STREAM_SYNC_STRING, sizeof(FLAC__STREAM_SYNC_STRING))) input_format = FORMAT_FLAC; - /* this could be made more accurate by looking at the first packet */ + /*@@@ this could be made more accurate by looking at the first packet to make sure it's Ogg FLAC and not, say, Ogg Vorbis. we do catch such problems later though. */ else if(!memcmp(lookahead, "OggS", 4)) input_format = FORMAT_OGGFLAC; else { + /* didn't find header of any supported format */ if(input_format != FORMAT_RAW) { format_mistake(infilename, input_format, FORMAT_RAW); if(option_values.treat_warnings_as_errors) { @@ -1723,6 +1742,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ return 1; } } + /* force to raw */ input_format = FORMAT_RAW; } } @@ -1733,9 +1753,9 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ conditional_fclose(encode_infile); return usage_error("ERROR: --keep-foreign-metadata cannot be used when encoding from stdin or to stdout\n"); } - if(input_format != FORMAT_WAVE && input_format != FORMAT_AIFF && input_format != FORMAT_AIFF_C) { + if(input_format != FORMAT_WAVE && input_format != FORMAT_RF64 && input_format != FORMAT_AIFF && input_format != FORMAT_AIFF_C) { conditional_fclose(encode_infile); - return usage_error("ERROR: --keep-foreign-metadata can only be used with WAVE or AIFF input\n"); + return usage_error("ERROR: --keep-foreign-metadata can only be used with WAVE, RF64, or AIFF input\n"); } } @@ -1889,12 +1909,18 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ else if(input_format == FORMAT_FLAC || input_format == FORMAT_OGGFLAC) { retval = flac__encode_file(encode_infile, infilesize, infilename, internal_outfilename? internal_outfilename : outfilename, lookahead, lookahead_length, encode_options); } - else if(input_format == FORMAT_WAVE || input_format == FORMAT_AIFF || input_format == FORMAT_AIFF_C) { + else if(input_format == FORMAT_WAVE || input_format == FORMAT_RF64 || input_format == FORMAT_AIFF || input_format == FORMAT_AIFF_C) { encode_options.format_options.iff.foreign_metadata = 0; - /* read foreign metadata if requested */ + /* initialize foreign metadata if requested */ if(option_values.keep_foreign_metadata) { - if(0 == (encode_options.format_options.iff.foreign_metadata = flac__foreign_metadata_new(input_format==FORMAT_WAVE? FOREIGN_BLOCK_TYPE__RIFF : FOREIGN_BLOCK_TYPE__AIFF))) { + encode_options.format_options.iff.foreign_metadata = + flac__foreign_metadata_new( + input_format==FORMAT_WAVE || input_format==FORMAT_RF64? + FOREIGN_BLOCK_TYPE__RIFF : + FOREIGN_BLOCK_TYPE__AIFF + ); + if(0 == encode_options.format_options.iff.foreign_metadata) { flac__utils_printf(stderr, 1, "ERROR: creating foreign metadata object\n"); conditional_fclose(encode_infile); return 1; @@ -1965,7 +1991,7 @@ int decode_file(const char *infilename) int retval; FLAC__bool treat_as_ogg = false; FileFormat output_format = FORMAT_WAVE; - decode_options_t common_options; + decode_options_t decode_options; const char *outfilename = get_decoded_outfilename(infilename); if(0 == outfilename) { @@ -1990,6 +2016,11 @@ int decode_file(const char *infilename) (strlen(outfilename) >= 5 && 0 == FLAC__STRCASECMP(outfilename+(strlen(outfilename)-5), ".aiff")) ) output_format = FORMAT_AIFF; + else if( + option_values.force_rf64_format || + (strlen(outfilename) >= 5 && 0 == FLAC__STRCASECMP(outfilename+(strlen(outfilename)-5), ".rf64")) + ) + output_format = FORMAT_RF64; else output_format = FORMAT_WAVE; @@ -2001,8 +2032,8 @@ int decode_file(const char *infilename) if(option_values.keep_foreign_metadata) { if(0 == strcmp(infilename, "-") || 0 == strcmp(outfilename, "-")) return usage_error("ERROR: --keep-foreign-metadata cannot be used when decoding from stdin or to stdout\n"); - if(output_format != FORMAT_WAVE && output_format != FORMAT_AIFF) - return usage_error("ERROR: --keep-foreign-metadata can only be used with WAVE or AIFF output\n"); + if(output_format != FORMAT_WAVE && output_format != FORMAT_RF64 && output_format != FORMAT_AIFF && output_format != FORMAT_AIFF_C) + return usage_error("ERROR: --keep-foreign-metadata can only be used with WAVE, RF64, or AIFF output\n"); } if(option_values.use_ogg) @@ -2021,63 +2052,61 @@ int decode_file(const char *infilename) } #endif - if(!flac__utils_parse_skip_until_specification(option_values.skip_specification, &common_options.skip_specification) || common_options.skip_specification.is_relative) + if(!flac__utils_parse_skip_until_specification(option_values.skip_specification, &decode_options.skip_specification) || decode_options.skip_specification.is_relative) return usage_error("ERROR: invalid value for --skip\n"); - if(!flac__utils_parse_skip_until_specification(option_values.until_specification, &common_options.until_specification)) /*@@@ more checks: no + without --skip, no - unless known total_samples_to_{en,de}code */ + if(!flac__utils_parse_skip_until_specification(option_values.until_specification, &decode_options.until_specification)) /*@@@ more checks: no + without --skip, no - unless known total_samples_to_{en,de}code */ return usage_error("ERROR: invalid value for --until\n"); /* if there is no "--until" we want to default to "--until=-0" */ if(0 == option_values.until_specification) - common_options.until_specification.is_relative = true; + decode_options.until_specification.is_relative = true; if(option_values.cue_specification) { - if(!flac__utils_parse_cue_specification(option_values.cue_specification, &common_options.cue_specification)) + if(!flac__utils_parse_cue_specification(option_values.cue_specification, &decode_options.cue_specification)) return usage_error("ERROR: invalid value for --cue\n"); - common_options.has_cue_specification = true; + decode_options.has_cue_specification = true; } else - common_options.has_cue_specification = false; + decode_options.has_cue_specification = false; - common_options.treat_warnings_as_errors = option_values.treat_warnings_as_errors; - common_options.continue_through_decode_errors = option_values.continue_through_decode_errors; - common_options.replaygain_synthesis_spec = option_values.replaygain_synthesis_spec; + decode_options.treat_warnings_as_errors = option_values.treat_warnings_as_errors; + decode_options.continue_through_decode_errors = option_values.continue_through_decode_errors; + decode_options.replaygain_synthesis_spec = option_values.replaygain_synthesis_spec; #if FLAC__HAS_OGG - common_options.is_ogg = treat_as_ogg; - common_options.use_first_serial_number = !option_values.has_serial_number; - common_options.serial_number = option_values.serial_number; + decode_options.is_ogg = treat_as_ogg; + decode_options.use_first_serial_number = !option_values.has_serial_number; + decode_options.serial_number = option_values.serial_number; #endif - common_options.channel_map_none = option_values.channel_map_none; + decode_options.channel_map_none = option_values.channel_map_none; + decode_options.format = output_format; if(output_format == FORMAT_RAW) { - raw_decode_options_t options; + decode_options.format_options.raw.is_big_endian = option_values.format_is_big_endian; + decode_options.format_options.raw.is_unsigned_samples = option_values.format_is_unsigned_samples; - options.common = common_options; - options.is_big_endian = option_values.format_is_big_endian; - options.is_unsigned_samples = option_values.format_is_unsigned_samples; - - retval = flac__decode_raw(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, options); + retval = flac__decode_file(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, decode_options); } else { - wav_decode_options_t options; - - options.common = common_options; - options.foreign_metadata = 0; + decode_options.format_options.iff.foreign_metadata = 0; - /* read foreign metadata if requested */ + /* initialize foreign metadata if requested */ if(option_values.keep_foreign_metadata) { - if(0 == (options.foreign_metadata = flac__foreign_metadata_new(output_format==FORMAT_WAVE? FOREIGN_BLOCK_TYPE__RIFF : FOREIGN_BLOCK_TYPE__AIFF))) { + decode_options.format_options.iff.foreign_metadata = + flac__foreign_metadata_new( + output_format==FORMAT_WAVE || output_format==FORMAT_RF64? + FOREIGN_BLOCK_TYPE__RIFF : + FOREIGN_BLOCK_TYPE__AIFF + ); + if(0 == decode_options.format_options.iff.foreign_metadata) { flac__utils_printf(stderr, 1, "ERROR: creating foreign metadata object\n"); return 1; } } - if(output_format == FORMAT_WAVE) - retval = flac__decode_wav(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, options); - else - retval = flac__decode_aiff(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, options); + retval = flac__decode_file(infilename, option_values.test_only? 0 : outfilename, option_values.analyze, option_values.aopts, decode_options); - if(options.foreign_metadata) - flac__foreign_metadata_delete(options.foreign_metadata); + if(decode_options.format_options.iff.foreign_metadata) + flac__foreign_metadata_delete(decode_options.format_options.iff.foreign_metadata); } if(retval == 0 && strcmp(infilename, "-")) { @@ -2108,6 +2137,9 @@ const char *get_decoded_outfilename(const char *infilename) else if(option_values.force_aiff_format) { suffix = ".aiff"; } + else if(option_values.force_rf64_format) { + suffix = ".rf64"; + } else { suffix = ".wav"; } diff --git a/src/flac/utils.c b/src/flac/utils.c index 28b1ebb..4dcd038 100644 --- a/src/flac/utils.c +++ b/src/flac/utils.c @@ -33,6 +33,16 @@ const char *CHANNEL_MASK_TAG = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK"; int flac__utils_verbosity_ = 2; +FLAC__bool flac__utils_format_is_iff(FileFormat format) +{ + return + format == FORMAT_WAVE || + format == FORMAT_RF64 || + format == FORMAT_AIFF || + format == FORMAT_AIFF_C + ; +} + static FLAC__bool local__parse_uint64_(const char *s, FLAC__uint64 *value) { FLAC__uint64 ret = 0; diff --git a/src/flac/utils.h b/src/flac/utils.h index 69c3443..368d1e4 100644 --- a/src/flac/utils.h +++ b/src/flac/utils.h @@ -27,7 +27,10 @@ #include "FLAC/format.h" /* for FLAC__StreamMetadata_CueSheet */ #include /* for FILE */ -typedef enum { FORMAT_RAW, FORMAT_WAVE, FORMAT_AIFF, FORMAT_AIFF_C, FORMAT_FLAC, FORMAT_OGGFLAC } FileFormat; +typedef enum { FORMAT_RAW, FORMAT_WAVE, FORMAT_RF64, FORMAT_AIFF, FORMAT_AIFF_C, FORMAT_FLAC, FORMAT_OGGFLAC } FileFormat; + +/* returns true iff format is one of FORMAT_WAVE, FORMAT_RF64, FORMAT_AIFF, FORMAT_AIFF_C */ +FLAC__bool flac__utils_format_is_iff(FileFormat format); typedef struct { FLAC__bool is_relative; /* i.e. specification string started with + or - */ diff --git a/src/test_streams/main.c b/src/test_streams/main.c index 0d9a528..f91c9e5 100644 --- a/src/test_streams/main.c +++ b/src/test_streams/main.c @@ -101,6 +101,20 @@ static FLAC__bool write_little_endian_int32(FILE *f, FLAC__int32 x) } #endif +static FLAC__bool write_little_endian_uint64(FILE *f, FLAC__uint64 x) +{ + return + fputc(x, f) != EOF && + fputc(x >> 8, f) != EOF && + fputc(x >> 16, f) != EOF && + fputc(x >> 24, f) != EOF && + fputc(x >> 32, f) != EOF && + fputc(x >> 40, f) != EOF && + fputc(x >> 48, f) != EOF && + fputc(x >> 56, f) != EOF + ; +} + static FLAC__bool write_big_endian(FILE *f, FLAC__int32 x, size_t bytes) { if(bytes < 4) @@ -668,7 +682,7 @@ foo: return false; } -static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsigned channels, unsigned bps, unsigned samples, FLAC__bool strict) +static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsigned channels, unsigned bps, unsigned samples, FLAC__bool strict, FLAC__bool rf64) { const FLAC__bool waveformatextensible = strict && (channels > 2 || (bps%8)); /* ^^^^^^^ @@ -689,11 +703,27 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig if(0 == (f = fopen(filename, "wb"))) return false; - if(fwrite("RIFF", 1, 4, f) < 4) + if(fwrite(rf64?"RF64":"RIFF", 1, 4, f) < 4) goto foo; - if(!write_little_endian_uint32(f, padded_size + (waveformatextensible?60:36))) + if(!write_little_endian_uint32(f, rf64? 0xffffffff : padded_size + (waveformatextensible?60:36))) goto foo; - if(fwrite("WAVEfmt ", 1, 8, f) < 8) + if(fwrite("WAVE", 1, 4, f) < 4) + goto foo; + if(rf64) { + if(fwrite("ds64", 1, 4, f) < 4) + goto foo; + if(!write_little_endian_uint32(f, 28)) /* ds64 chunk size */ + goto foo; + if(!write_little_endian_uint64(f, 36 + padded_size + (waveformatextensible?60:36))) + goto foo; + if(!write_little_endian_uint64(f, true_size)) + goto foo; + if(!write_little_endian_uint64(f, samples)) + goto foo; + if(!write_little_endian_uint32(f, 0)) /* table size */ + goto foo; + } + if(fwrite("fmt ", 1, 4, f) < 4) goto foo; if(!write_little_endian_uint32(f, waveformatextensible?40:16)) goto foo; @@ -722,7 +752,7 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig } if(fwrite("data", 1, 4, f) < 4) goto foo; - if(!write_little_endian_uint32(f, true_size)) + if(!write_little_endian_uint32(f, rf64? 0xffffffff : true_size)) goto foo; for(i = 0, theta1 = theta2 = 0.0; i < samples; i++, theta1 += delta1, theta2 += delta2) { @@ -749,12 +779,12 @@ static FLAC__bool generate_wackywavs(void) FILE *f; FLAC__byte wav[] = { 'R', 'I', 'F', 'F', 76, 0, 0, 0, - 'W', 'A', 'V', 'E', 'f', 'a', 'c', 't', + 'W', 'A', 'V', 'E', 'j', 'u', 'n', 'k', 4, 0, 0, 0 , 'b', 'l', 'a', 'h', 'p', 'a', 'd', ' ', 4, 0, 0, 0, 'B', 'L', 'A', 'H', 'f', 'm', 't', ' ', 16, 0, 0, 0, 1, 0, 1, 0, - 0x44,0xAC, 0, 0, 0, 0, 0, 0, + 0x44,0xAC, 0, 0,0x88,0x58,0x01, 0, 2, 0, 16, 0, 'd', 'a', 't', 'a', 16, 0, 0, 0, 0, 0, 1, 0, 4, 0, 9, 0, 16, 0, 25, 0, @@ -781,6 +811,48 @@ foo: return false; } +static FLAC__bool generate_wackyrf64s(void) +{ + FILE *f; + FLAC__byte wav[] = { + 'R', 'F', '6', '4', 255, 255, 255, 255, + 'W', 'A', 'V', 'E', 'd', 's', '6', '4', + 28, 0, 0, 0, 112, 0, 0, 0, + 0, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 'j', 'u', 'n', 'k', + 4, 0, 0, 0, 'b', 'l', 'a', 'h', + 'p', 'a', 'd', ' ', 4, 0, 0, 0, + 'B', 'L', 'A', 'H', 'f', 'm', 't', ' ', + 16, 0, 0, 0, 1, 0, 1, 0, + 0x44,0xAC, 0, 0,0x88,0x58,0x01, 0, + 2, 0, 16, 0, 'd', 'a', 't', 'a', + 255, 255, 255, 255, 0, 0, 1, 0, + 4, 0, 9, 0, 16, 0, 25, 0, + 36, 0, 49, 0, 'p', 'a', 'd', ' ', + 4, 0, 0, 0, 'b', 'l', 'a', 'h' + }; + + if(0 == (f = fopen("wacky1.rf64", "wb"))) + return false; + if(fwrite(wav, 1, 120, f) < 120) + goto foo; + fclose(f); + + wav[20] += 12; + if(0 == (f = fopen("wacky2.rf64", "wb"))) + return false; + if(fwrite(wav, 1, 132, f) < 132) + goto foo; + fclose(f); + + return true; +foo: + fclose(f); + return false; +} + int main(int argc, char *argv[]) { FLAC__uint32 test = 1; @@ -898,6 +970,7 @@ int main(int argc, char *argv[]) if(!generate_noise("noise.raw", 65536 * 8 * 3)) return 1; if(!generate_noise("noise8m32.raw", 32)) return 1; if(!generate_wackywavs()) return 1; + if(!generate_wackyrf64s()) return 1; for(channels = 1; channels <= 8; channels++) { unsigned bits_per_sample; for(bits_per_sample = 4; bits_per_sample <= 24; bits_per_sample++) { @@ -911,7 +984,11 @@ int main(int argc, char *argv[]) return 1; sprintf(fn, "rt-%u-%u-%u.wav", channels, bits_per_sample, nsamples[samples]); - if(!generate_wav(fn, 44100, channels, bits_per_sample, nsamples[samples], /*strict=*/true)) + if(!generate_wav(fn, 44100, channels, bits_per_sample, nsamples[samples], /*strict=*/true, /*rf64=*/false)) + return 1; + + sprintf(fn, "rt-%u-%u-%u.rf64", channels, bits_per_sample, nsamples[samples]); + if(!generate_wav(fn, 44100, channels, bits_per_sample, nsamples[samples], /*strict=*/true, /*rf64=*/true)) return 1; if(bits_per_sample % 8 == 0) { diff --git a/test/Makefile.am b/test/Makefile.am index be64def..370d10a 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -45,7 +45,8 @@ EXTRA_DIST = \ test_grabbag.sh \ test_seeking.sh \ test_streams.sh \ - test_bins.sh + test_bins.sh \ + write_iff.pl clean-local: - -rm -f *.raw *.flac *.oga *.ogg *.cmp *.aiff *.wav *.diff *.log *.cue core + -rm -f *.raw *.flac *.oga *.ogg *.cmp *.aiff *.wav *.rf64 *.diff *.log *.cue core diff --git a/test/Makefile.lite b/test/Makefile.lite index ff4fbae..08bb189 100644 --- a/test/Makefile.lite +++ b/test/Makefile.lite @@ -52,4 +52,4 @@ valgrind: all release : all clean: - rm -f *.raw *.flac *.oga *.ogg *.cmp *.aiff *.wav *.diff *.log *.cue core flac-to-flac-metadata-test-files/out.* metaflac-test-files/out.* + rm -f *.raw *.flac *.oga *.ogg *.cmp *.aiff *.wav *.rf64 *.diff *.log *.cue core flac-to-flac-metadata-test-files/out.* metaflac-test-files/out.* diff --git a/test/test_flac.sh b/test/test_flac.sh index 0598f1d..c208d96 100755 --- a/test/test_flac.sh +++ b/test/test_flac.sh @@ -183,12 +183,13 @@ done rt_test_raw () { f="$1" + extra="$2" channels=`echo $f | awk -F- '{print $2}'` bps=`echo $f | awk -F- '{print $3}'` echo -n "round-trip test ($f) encode... " - run_flac $SILENT --force --verify --force-raw-format --endian=little --sign=signed --sample-rate=44100 --bps=$bps --channels=$channels --no-padding --lax -o rt.flac $f || die "ERROR" + run_flac $SILENT --force --verify --force-raw-format --endian=little --sign=signed --sample-rate=44100 --bps=$bps --channels=$channels --no-padding --lax -o rt.flac $extra $f || die "ERROR" echo -n "decode... " - run_flac $SILENT --force --decode --force-raw-format --endian=little --sign=signed -o rt.raw rt.flac || die "ERROR" + run_flac $SILENT --force --decode --force-raw-format --endian=little --sign=signed -o rt.raw $extra rt.flac || die "ERROR" echo -n "compare... " cmp $f rt.raw || die "ERROR: file mismatch" echo "OK" @@ -198,23 +199,39 @@ rt_test_raw () rt_test_wav () { f="$1" + extra="$2" echo -n "round-trip test ($f) encode... " - run_flac $SILENT --force --verify --channel-map=none --no-padding --lax -o rt.flac $f || die "ERROR" + run_flac $SILENT --force --verify --channel-map=none --no-padding --lax -o rt.flac $extra $f || die "ERROR" echo -n "decode... " - run_flac $SILENT --force --decode --channel-map=none -o rt.wav rt.flac || die "ERROR" + run_flac $SILENT --force --decode --channel-map=none -o rt.wav $extra rt.flac || die "ERROR" echo -n "compare... " cmp $f rt.wav || die "ERROR: file mismatch" echo "OK" rm -f rt.flac rt.wav } +rt_test_rf64 () +{ + f="$1" + extra="$2" + echo -n "round-trip test ($f) encode... " + run_flac $SILENT --force --verify --channel-map=none --no-padding --lax -o rt.flac $extra $f || die "ERROR" + echo -n "decode... " + run_flac $SILENT --force --decode --channel-map=none -o rt.rf64 $extra rt.flac || die "ERROR" + echo -n "compare... " + cmp $f rt.rf64 || die "ERROR: file mismatch" + echo "OK" + rm -f rt.flac rt.rf64 +} + rt_test_aiff () { f="$1" + extra="$2" echo -n "round-trip test ($f) encode... " - run_flac $SILENT --force --verify --channel-map=none --no-padding --lax -o rt.flac $f || die "ERROR" + run_flac $SILENT --force --verify --channel-map=none --no-padding --lax -o rt.flac $extra $f || die "ERROR" echo -n "decode... " - run_flac $SILENT --force --decode --channel-map=none -o rt.aiff rt.flac || die "ERROR" + run_flac $SILENT --force --decode --channel-map=none -o rt.aiff $extra rt.flac || die "ERROR" echo -n "compare... " cmp $f rt.aiff || die "ERROR: file mismatch" echo "OK" @@ -225,12 +242,13 @@ rt_test_aiff () rt_test_flac () { f="$1" + extra="$2" echo -n "round-trip test ($f->flac->flac->wav) encode... " - run_flac $SILENT --force --verify --channel-map=none --no-padding --lax -o rt.flac $f || die "ERROR" + run_flac $SILENT --force --verify --channel-map=none --no-padding --lax -o rt.flac $extra $f || die "ERROR" echo -n "re-encode... " run_flac $SILENT --force --verify --lax -o rt2.flac rt.flac || die "ERROR" echo -n "decode... " - run_flac $SILENT --force --decode --channel-map=none -o rt.wav rt2.flac || die "ERROR" + run_flac $SILENT --force --decode --channel-map=none -o rt.wav $extra rt2.flac || die "ERROR" echo -n "compare... " cmp $f rt.wav || die "ERROR: file mismatch" echo "OK" @@ -241,12 +259,13 @@ rt_test_flac () rt_test_ogg_flac () { f="$1" + extra="$2" echo -n "round-trip test ($f->oggflac->oggflac->wav) encode... " - run_flac $SILENT --force --verify --channel-map=none --no-padding --lax -o rt.oga --ogg $f || die "ERROR" + run_flac $SILENT --force --verify --channel-map=none --no-padding --lax -o rt.oga --ogg $extra $f || die "ERROR" echo -n "re-encode... " run_flac $SILENT --force --verify --lax -o rt2.oga --ogg rt.oga || die "ERROR" echo -n "decode... " - run_flac $SILENT --force --decode --channel-map=none -o rt.wav rt2.oga || die "ERROR" + run_flac $SILENT --force --decode --channel-map=none -o rt.wav $extra rt2.oga || die "ERROR" echo -n "compare... " cmp $f rt.wav || die "ERROR: file mismatch" echo "OK" @@ -259,6 +278,9 @@ done for f in rt-*.wav ; do rt_test_wav $f done +for f in rt-*.rf64 ; do + rt_test_rf64 $f +done for f in rt-*.aiff ; do rt_test_aiff $f done @@ -1117,6 +1139,18 @@ done ############################################################################ +# test --keep-foreign-metadata +############################################################################ + +echo "Testing --keep-foreign-metadata..." + +rt_test_wav wacky1.wav '--keep-foreign-metadata' +rt_test_wav wacky2.wav '--keep-foreign-metadata' +rt_test_rf64 wacky1.rf64 '--keep-foreign-metadata' +rt_test_rf64 wacky2.rf64 '--keep-foreign-metadata' + + +############################################################################ # test the metadata-handling properties of flac-to-flac encoding ############################################################################ diff --git a/test/write_iff.pl b/test/write_iff.pl new file mode 100755 index 0000000..83132fb --- /dev/null +++ b/test/write_iff.pl @@ -0,0 +1,168 @@ +#!/usr/bin/perl -w + +use strict; + +require Math::BigInt; + +my $usage = " +$0 <#samples> + + is one of aiff,wave,rf64 + is 8,16,24,32 + is 1-8 + is any 32-bit value + <#samples> is 0-2^64-1 + is one of zero,rand + +"; + +die $usage unless @ARGV == 6; + +my %formats = ( 'aiff'=>1, 'wave'=>1, 'rf64'=>1 ); +my %sampletypes = ( 'zero'=>1, 'rand'=>1 ); +my @channelmask = ( 0, 1, 3, 7, 0x33, 0x607, 0x60f, 0, 0 ); #@@@@@@ need proper masks for 7,8 + +my ($format, $bps, $channels, $samplerate, $samples, $sampletype) = @ARGV; +my $bigsamples = new Math::BigInt $samples; + +die $usage unless defined $formats{$format}; +die $usage unless $bps == 8 || $bps == 16 || $bps == 24 || $bps == 32; +die $usage unless $channels >= 1 && $channels <= 8; +die $usage unless $samplerate >= 0 && $samplerate <= 4294967295; +die $usage unless defined $sampletypes{$sampletype}; + +# convert bits-per-sample to bytes-per-sample +$bps /= 8; + +my $datasize = $samples * $bps * $channels; +my $bigdatasize = $bigsamples * $bps * $channels; + +my $padding = int($bigdatasize & 1? 1 : 0); +my $wavx = ($format eq 'wave' || $format eq 'rf64') && ($channels > 2); + +# write header + +if ($format eq 'aiff') { + die "sample data too big for format\n" if 46 + $datasize + $padding > 4294967295; + # header + print "FORM"; + print pack('N', 46 + $datasize + $padding); + print "AIFF"; + # COMM chunk + print "COMM"; + print pack('N', 18); # chunk size = 18 + print pack('n', $channels); + print pack('N', $samples); + print pack('n', $bps * 8); + print pack_sane_extended($samplerate); + # SSND header + print "SSND"; + print pack('N', $datasize + 8); # chunk size + print pack('N', 0); # ssnd_offset_size + print pack('N', 0); # blocksize +} +elsif ($format eq 'wave' || $format eq 'rf64') { + die "sample data too big for format\n" if $format eq 'wave' && ($wavx?60:36) + $datasize + $padding > 4294967295; + # header + if ($format eq 'wave') { + print "RIFF"; + print pack('V', ($wavx?60:36) + $datasize + $padding); + print "WAVE"; + } + else { + print "RF64"; + print pack('V', 0xffffffff); + print "WAVE"; + # ds64 chunk + print "ds64"; + print pack('V', 28); # chunk size + my $bigriffsize = $bigdatasize + ($wavx?60:36) + (8+28) + $padding; + print pack_64('V', $bigriffsize); + print pack_64('V', $bigdatasize); + print pack_64('V', $bigsamples); + print pack('V', 0); # table size + } + # fmt chunk + print "fmt "; + print pack('V', $wavx?40:16); # chunk size + print pack('v', $wavx?65534:1); # compression code + print pack('v', $channels); + print pack('V', $samplerate); + print pack('V', $samplerate * $channels * $bps); + print pack('v', $bps); # block align = channels*((bps+7)/8) + print pack('v', $bps * 8); # bits per sample = ((bps+7)/8)*8 + if ($wavx) { + print pack('v', 22); # cbSize + print pack('v', $bps * 8); # validBitsPerSample + print pack('V', $channelmask[$channels]); + # GUID = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}} + print "\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71"; + } + # data header + print "data"; + print pack('V', $format eq 'wave'? $datasize : 0xffffffff); +} +else { + die; +} + +# write sample data + +if ($sampletype eq 'zero') { + my $chunk = 4096; + my $buf = pack("x[".($channels*$bps*$chunk)."]"); + for (my $s = $samples; $s > 0; $s -= $chunk) { + if ($s < $chunk) { + print substr($buf, 0, $channels*$bps*$s); + } + else { + print $buf; + } + } +} +elsif ($sampletype eq 'rand') { + for (my $s = 0; $s < $samples; $s++) { + for (my $c = 0; $c < $channels; $c++) { + for (my $b = 0; $b < $bps; $b++) { + print pack('C', int(rand(256))); + } + } + } +} +else { + die; +} +print "\x00" if $padding; + +exit 0; + +sub pack_sane_extended +{ + my $val = shift; + die unless $val > 0; + my $shift; + for ($shift = 0; ($val>>(31-$shift)) == 0; ++$shift) { + } + $val <<= $shift; + my $exponent = 63 - ($shift + 32); + return pack('nNN', $exponent + 16383, $val, 0); +} + +sub pack_64 +{ + my $c = shift; + my $v1 = shift; + my $v2 = $v1->copy(); + if ($c eq 'V') { + $v1->band(0xffffffff); + $v2->brsft(32); + } + elsif ($c eq 'C') { + $v2->band(0xffffffff); + $v1->brsft(32); + } + else { + die; + } + return pack("$c$c", 0+$v1->bstr(), 0+$v2->bstr()); +} -- 2.7.4