From cc5c1d8837185ac60630fd8cc78cf1585fa84720 Mon Sep 17 00:00:00 2001 From: Josh Coalson Date: Fri, 6 Oct 2006 05:31:27 +0000 Subject: [PATCH] complete WAVEFORMATEXTENSIBLE support, multichannel assignments in format and documentation, multichannel support for AIFF and WAVE, channel mapping, new --channel-map=none option to flac, saving and restoring of WAVEFORMATEXTENSIBLE channel mask to/from tag, robust handling of "odd" sample resolutions (i.e. not multiple of 8 bits) for AIFF and WAVE --- src/flac/decode.c | 79 +++++++++-- src/flac/decode.h | 1 + src/flac/encode.c | 284 ++++++++++++++++++++++++++++++---------- src/flac/encode.h | 1 + src/flac/main.c | 10 ++ src/flac/utils.c | 40 ++++++ src/flac/utils.h | 3 + src/test_streams/main.c | 24 +++- test/test_flac.sh | 12 +- 9 files changed, 364 insertions(+), 90 deletions(-) diff --git a/src/flac/decode.c b/src/flac/decode.c index 76395572..49d149ad 100644 --- a/src/flac/decode.c +++ b/src/flac/decode.c @@ -54,6 +54,7 @@ typedef struct { FLAC__bool is_aiff_out; FLAC__bool is_wave_out; FLAC__bool continue_through_decode_errors; + FLAC__bool channel_map_none; struct { replaygain_synthesis_spec_t spec; @@ -87,6 +88,7 @@ typedef struct { unsigned bps; unsigned channels; unsigned sample_rate; + FLAC__uint32 channel_mask; union { FLAC__StreamDecoder *flac; @@ -105,7 +107,7 @@ static FLAC__bool is_big_endian_host_; /* * local routines */ -static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, 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, const char *infilename, const char *outfilename); +static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, 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, const char *infilename, const char *outfilename); static void DecoderSession_destroy(DecoderSession *d, FLAC__bool error_occurred); static FLAC__bool DecoderSession_init_decoder(DecoderSession *d, decode_options_t decode_options, const char *infilename); static FLAC__bool DecoderSession_process(DecoderSession *d); @@ -145,6 +147,7 @@ int flac__decode_aiff(const char *infilename, const char *outfilename, FLAC__boo /*is_aiff_out=*/true, /*is_wave_out=*/false, options.common.continue_through_decode_errors, + options.common.channel_map_none, options.common.replaygain_synthesis_spec, analysis_mode, aopts, @@ -181,6 +184,7 @@ int flac__decode_wav(const char *infilename, const char *outfilename, FLAC__bool /*is_aiff_out=*/false, /*is_wave_out=*/true, options.common.continue_through_decode_errors, + options.common.channel_map_none, options.common.replaygain_synthesis_spec, analysis_mode, aopts, @@ -220,6 +224,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool /*is_aiff_out=*/false, /*is_wave_out=*/false, options.common.continue_through_decode_errors, + options.common.channel_map_none, options.common.replaygain_synthesis_spec, analysis_mode, aopts, @@ -241,7 +246,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 is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, 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, const char *infilename, const char *outfilename) +FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, 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, const char *infilename, const char *outfilename) { #ifdef FLAC__HAS_OGG d->is_ogg = is_ogg; @@ -252,6 +257,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__ d->is_aiff_out = is_aiff_out; d->is_wave_out = is_wave_out; d->continue_through_decode_errors = continue_through_decode_errors; + d->channel_map_none = channel_map_none; d->replaygain.spec = replaygain_synthesis_spec; d->replaygain.apply = false; d->replaygain.scale = 0.0; @@ -279,6 +285,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__ d->bps = 0; d->channels = 0; d->sample_rate = 0; + d->channel_mask = 0; d->decoder.flac = 0; #ifdef FLAC__HAS_OGG @@ -409,6 +416,36 @@ FLAC__bool DecoderSession_process(DecoderSession *d) if(d->abort_flag) return false; + /* set channel mapping */ + if(!d->channel_map_none) { + /* currently FLAC order matches SMPTE/WAVEFORMATEXTENSIBLE order, so no reordering is necessary; see encode.c */ + /* only the channel mask must be set if it was not already picked up from the WAVEFORMATEXTENSIBLE_CHANNEL_MASK tag */ + if(d->channels == 1) { + if(d->channel_mask == 0) + d->channel_mask = 0x0001; + } + else if(d->channels == 2) { + if(d->channel_mask == 0) + d->channel_mask = 0x0003; + } + else if(d->channels == 3) { + if(d->channel_mask == 0) + d->channel_mask = 0x0007; + } + else if(d->channels == 4) { + if(d->channel_mask == 0) + d->channel_mask = 0x0033; + } + else if(d->channels == 5) { + if(d->channel_mask == 0) + d->channel_mask = 0x0607; + } + else if(d->channels == 6) { + if(d->channel_mask == 0) + d->channel_mask = 0x060f; + } + } + /* write the WAVE/AIFF headers if necessary */ if(!d->analysis_mode && !d->test_only && (d->is_wave_out || d->is_aiff_out)) { if(!write_iff_headers(d->fout, d, d->total_samples)) { @@ -610,6 +647,7 @@ 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 */ if(samples == 0) { @@ -630,16 +668,16 @@ FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uin if(flac__utils_fwrite("RIFF", 1, 4, f) != 4) return false; - if(!write_little_endian_uint32(f, aligned_data_size+36)) /* filesize-8 */ + if(!write_little_endian_uint32(f, aligned_data_size+(is_waveformatextensible?60:36))) /* filesize-8 */ return false; if(flac__utils_fwrite("WAVEfmt ", 1, 8, f) != 8) return false; - if(flac__utils_fwrite("\020\000\000\000", 1, 4, f) != 4) /* chunk size = 16 */ + if(!write_little_endian_uint32(f, is_waveformatextensible? 40 : 16)) /* chunk size */ return false; - if(flac__utils_fwrite("\001\000", 1, 2, f) != 2) /* compression code == 1 */ + if(!write_little_endian_uint16(f, (FLAC__uint16)(is_waveformatextensible? 65534 : 1))) /* compression code */ return false; if(!write_little_endian_uint16(f, (FLAC__uint16)(decoder_session->channels))) @@ -654,9 +692,24 @@ FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uin if(!write_little_endian_uint16(f, (FLAC__uint16)(decoder_session->channels * ((decoder_session->bps+7) / 8)))) /* block align */ return false; - if(!write_little_endian_uint16(f, (FLAC__uint16)(decoder_session->bps))) /* bits per sample */ + if(!write_little_endian_uint16(f, (FLAC__uint16)(((decoder_session->bps+7)/8)*8))) /* bits per sample */ return false; + if(is_waveformatextensible) { + if(!write_little_endian_uint16(f, (FLAC__uint16)22)) /* cbSize */ + return false; + + if(!write_little_endian_uint16(f, (FLAC__uint16)decoder_session->bps)) /* validBitsPerSample */ + return false; + + if(!write_little_endian_uint32(f, decoder_session->channel_mask)) + return false; + + /* GUID = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}} */ + if(flac__utils_fwrite("\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71", 1, 16, f) != 16) + return false; + } + if(flac__utils_fwrite("data", 1, 4, f) != 4) return false; @@ -801,6 +854,7 @@ 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 && (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)); unsigned wide_samples = frame->header.blocksize, wide_sample, sample, channel, byte; @@ -916,7 +970,12 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder flac__analyze_frame(frame, decoder_session->frame_counter-1, decoder_session->aopts, fout); } else if(!decoder_session->test_only) { - if (decoder_session->replaygain.apply) { + if(shift && !decoder_session->replaygain.apply) { + for(wide_sample = 0; wide_sample < wide_samples; wide_sample++) + for(channel = 0; channel < channels; channel++) + /*@@@@@@bad un-const:fix@@@@@@*/((FLAC__int32**)buffer)[channel][wide_sample] <<= shift; + } + if(decoder_session->replaygain.apply) { bytes_to_write = FLAC__replaygain_synthesis__apply_gain( u8buffer, !is_big_endian, @@ -925,7 +984,7 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder wide_samples, channels, bps, /* source_bps */ - bps, /* target_bps */ + bps+shift, /* target_bps */ decoder_session->replaygain.scale, decoder_session->replaygain.spec.limiter == RGSS_LIMIT__HARD, /* hard_limit */ decoder_session->replaygain.spec.noise_shaping != NOISE_SHAPING_NONE, /* do_dithering */ @@ -1014,6 +1073,9 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder } else { FLAC__ASSERT(0); + /* double protection */ + decoder_session->abort_flag = true; + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } } } @@ -1117,6 +1179,7 @@ void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMet flac__utils_printf(stderr, 1, "%s: WARNING: applying ReplayGain is not lossless\n", decoder_session->inbasefilename); } } + (void)flac__utils_get_channel_mask_tag(metadata, &decoder_session->channel_mask); } } diff --git a/src/flac/decode.h b/src/flac/decode.h index cc004200..42c8ca91 100644 --- a/src/flac/decode.h +++ b/src/flac/decode.h @@ -47,6 +47,7 @@ typedef struct { utils__SkipUntilSpecification until_specification; 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 */ diff --git a/src/flac/encode.c b/src/flac/encode.c index 2470549d..fb60b195 100644 --- a/src/flac/encode.c +++ b/src/flac/encode.c @@ -136,12 +136,12 @@ static FLAC__bool EncoderSession_construct(EncoderSession *e, FLAC__bool use_ogg static void EncoderSession_destroy(EncoderSession *e); static int EncoderSession_finish_ok(EncoderSession *e, int info_align_carry, int info_align_zero); static int EncoderSession_finish_error(EncoderSession *e); -static FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, unsigned channels, unsigned bps, unsigned sample_rate, FLACDecoderData *flac_decoder_data); +static FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, FLAC__uint32 channel_mask, unsigned channels, unsigned bps, unsigned sample_rate, FLACDecoderData *flac_decoder_data); static FLAC__bool EncoderSession_process(EncoderSession *e, const FLAC__int32 * const buffer[], unsigned samples); static FLAC__bool convert_to_seek_table_template(const char *requested_seek_points, int num_requested_seek_points, FLAC__StreamMetadata *cuesheet, EncoderSession *e); static FLAC__bool canonicalize_until_specification(utils__SkipUntilSpecification *spec, const char *inbasefilename, unsigned sample_rate, FLAC__uint64 skip, FLAC__uint64 total_samples_in_input); static FLAC__bool verify_metadata(const EncoderSession *e, FLAC__StreamMetadata **metadata, unsigned num_metadata); -static FLAC__bool format_input(FLAC__int32 *dest[], unsigned wide_samples, FLAC__bool is_big_endian, FLAC__bool is_unsigned_samples, unsigned channels, unsigned bps, unsigned shift); +static FLAC__bool format_input(FLAC__int32 *dest[], unsigned wide_samples, FLAC__bool is_big_endian, FLAC__bool is_unsigned_samples, unsigned channels, unsigned bps, unsigned shift, size_t *channel_map); static void encoder_progress_callback(const FLAC__StreamEncoder *encoder, FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate, void *client_data); static FLAC__StreamDecoderReadStatus flac_decoder_read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], unsigned *bytes, void *client_data); static FLAC__StreamDecoderSeekStatus flac_decoder_seek_callback(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data); @@ -173,7 +173,8 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con EncoderSession encoder_session; FLAC__uint16 x; FLAC__uint32 xx; - unsigned int channels= 0U, bps= 0U, sample_rate= 0U, sample_frames= 0U; + unsigned int channels= 0U, bps= 0U, shift= 0U, sample_rate= 0U, sample_frames= 0U; + size_t channel_map[FLAC__MAX_CHANNELS]; FLAC__bool got_comm_chunk= false, got_ssnd_chunk= false; int info_align_carry= -1, info_align_zero= -1; FLAC__bool is_big_endian_pcm = true; @@ -198,6 +199,13 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con ) return 1; + /* initialize default channel map that preserves channel order */ + { + size_t i; + for(i = 0; i < sizeof(channel_map)/sizeof(channel_map[0]); i++) + channel_map[i] = i; + } + /* lookahead[] already has "FORMxxxxAIFF", do sub-chunks */ while(1) { @@ -235,6 +243,10 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con flac__utils_printf(stderr, 1, "%s: ERROR: unsupported number channels %u\n", encoder_session.inbasefilename, (unsigned int)x); return EncoderSession_finish_error(&encoder_session); } + else if(x>2U && !options.common.channel_map_none) { + flac__utils_printf(stderr, 1, "%s: ERROR: unsupported number channels %u for AIFF\n", encoder_session.inbasefilename, (unsigned int)x); + return EncoderSession_finish_error(&encoder_session); + } else if(options.common.sector_align && x!=2U) { flac__utils_printf(stderr, 1, "%s: ERROR: file has %u channels, must be 2 for --sector-align\n", encoder_session.inbasefilename, (unsigned int)x); return EncoderSession_finish_error(&encoder_session); @@ -249,15 +261,17 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con /* bits per sample */ if(!read_big_endian_uint16(infile, &x, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); - else if(x!=8U && x!=16U && x!=24U) { - flac__utils_printf(stderr, 1, "%s: ERROR: unsupported bits per sample %u\n", encoder_session.inbasefilename, (unsigned int)x); + else if(x<4U || x>24U) { + flac__utils_printf(stderr, 1, "%s: ERROR: unsupported bits-per-sample %u\n", encoder_session.inbasefilename, (unsigned int)x); return EncoderSession_finish_error(&encoder_session); } else if(options.common.sector_align && x!=16U) { - flac__utils_printf(stderr, 1, "%s: ERROR: file has %u bits per sample, must be 16 for --sector-align\n", encoder_session.inbasefilename, (unsigned int)x); + flac__utils_printf(stderr, 1, "%s: ERROR: file has %u bits-per-sample, must be 16 for --sector-align\n", encoder_session.inbasefilename, (unsigned int)x); return EncoderSession_finish_error(&encoder_session); } bps= x; + shift= (bps%8)? 8-(bps%8) : 0; /* SSND data is always byte-aligned, left-justified but format_input() will double-check */ + bps+= shift; /* sample rate */ if(!read_sane_extended(infile, &xx, false, encoder_session.inbasefilename)) @@ -286,6 +300,35 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con } } + /* set channel mapping */ + /* FLAC order follows SMPTE and WAVEFORMATEXTENSIBLE but with fewer channels, which are: */ + /* front left, front right, center, LFE, back left, back right, surround left, surround right */ + /* specs say the channel ordering is: + * 1 2 3 4 5 6 + * ___________________________________________________ + * 2 stereo l r + * 3 l r c + * 4 l c r S + * quad (ambiguous with 4ch) Fl Fr Bl Br + * 5 Fl Fr Fc Sl Sr + * 6 l lc c r rc S + * l:left r:right c:center Fl:front-left Fr:front-right Bl:back-left Br:back-right Lc:left-center Rc:right-center S:surround + * so we only have unambiguous mappings for 2, 3, and 5 channels + */ + if( + options.common.channel_map_none || + channels == 1 || /* 1 channel: (mono) */ + channels == 2 || /* 2 channels: left, right */ + channels == 3 || /* 3 channels: left, right, center */ + channels == 5 /* 5 channels: front left, front right, center, surround left, surround right */ + ) { + /* keep default channel order */ + } + else { + flac__utils_printf(stderr, 1, "%s: ERROR: unsupported number channels %u for AIFF\n", encoder_session.inbasefilename, channels); + return EncoderSession_finish_error(&encoder_session); + } + /* skip any extra data in the COMM chunk */ if(!fskip_ahead(infile, skip)) { flac__utils_printf(stderr, 1, "%s: ERROR during read while skipping extra COMM data\n", encoder_session.inbasefilename); @@ -387,7 +430,7 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con /* +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*bytes_per_frame+54; - if(!EncoderSession_init_encoder(&encoder_session, options.common, channels, bps, sample_rate, /*flac_decoder_data=*/0)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, /*channel_mask=*/0, channels, bps-shift, sample_rate, /*flac_decoder_data=*/0)) return EncoderSession_finish_error(&encoder_session); /* first do any samples in the reservoir */ @@ -430,7 +473,7 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con } else { unsigned int frames= bytes_read/bytes_per_frame; - if(!format_input(input_, frames, is_big_endian_pcm, /*is_unsigned_samples=*/false, channels, bps, /*shift=*/0)) + if(!format_input(input_, frames, is_big_endian_pcm, /*is_unsigned_samples=*/false, channels, bps, shift, channel_map)) return EncoderSession_finish_error(&encoder_session); if(!EncoderSession_process(&encoder_session, (const FLAC__int32 *const *)input_, frames)) { @@ -483,7 +526,7 @@ int flac__encode_aif(FILE *infile, off_t infilesize, const char *infilename, con } else { info_align_carry= *options.common.align_reservoir_samples; - if(!format_input(options.common.align_reservoir, *options.common.align_reservoir_samples, is_big_endian_pcm, /*is_unsigned_samples=*/false, channels, bps, /*shift=*/0)) + if(!format_input(options.common.align_reservoir, *options.common.align_reservoir_samples, is_big_endian_pcm, /*is_unsigned_samples=*/false, channels, bps, shift, channel_map)) return EncoderSession_finish_error(&encoder_session); } } @@ -541,6 +584,7 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con FLAC__bool is_unsigned_samples = false; unsigned channels = 0, bps = 0, sample_rate = 0, shift = 0; size_t bytes_per_wide_sample, bytes_read; + size_t channel_map[FLAC__MAX_CHANNELS]; FLAC__uint16 x, format; /* format is the wFormatTag word from the 'fmt ' chunk */ FLAC__uint32 xx, channel_mask = 0; FLAC__bool got_fmt_chunk = false, got_data_chunk = false; @@ -567,6 +611,13 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con ) return 1; + /* initialize default channel map that preserves channel order */ + { + size_t i; + for(i = 0; i < sizeof(channel_map)/sizeof(channel_map[0]); i++) + channel_map[i] = i; + } + /* * lookahead[] already has "RIFFxxxxWAVE", do sub-chunks */ @@ -603,8 +654,8 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con * * Block align for WAVE_FORMAT_PCM or WAVE_FORMAT_EXTENSIBLE is also supposed to be channels*bps/8 * - * If the channel mask has less set bits that # of channels, the extra MSBs are ignored. - * If the channel mask has more set bits that # of channels, the extra channels are unassigned to any speaker. + * If the channel mask has more set bits than # of channels, the extra MSBs are ignored. + * If the channel mask has less set bits than # of channels, the extra channels are unassigned to any speaker. * * Data is supposed to be unsigned for bps <= 8 else signed. */ @@ -612,12 +663,12 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con /* fmt sub-chunk size */ if(!read_little_endian_uint32(infile, &xx, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); - if(xx < 16) { - flac__utils_printf(stderr, 1, "%s: ERROR: found non-standard 'fmt ' sub-chunk which has length = %u\n", encoder_session.inbasefilename, (unsigned)xx); + data_bytes = xx; + if(data_bytes < 16) { + flac__utils_printf(stderr, 1, "%s: ERROR: found non-standard 'fmt ' sub-chunk which has length = %u\n", encoder_session.inbasefilename, data_bytes); return EncoderSession_finish_error(&encoder_session); } - data_bytes = xx; - /* compression code */ + /* format code */ if(!read_little_endian_uint16(infile, &format, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); if(format != 1 /*WAVE_FORMAT_PCM*/ && format != 65534 /*WAVE_FORMAT_EXTENSIBLE*/) { @@ -627,57 +678,55 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con /* number of channels */ if(!read_little_endian_uint16(infile, &x, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); - if(x == 0 || x > FLAC__MAX_CHANNELS) { - flac__utils_printf(stderr, 1, "%s: ERROR: unsupported number of channels %u\n", encoder_session.inbasefilename, (unsigned)x); + channels = (unsigned)x; + if(channels == 0 || channels > FLAC__MAX_CHANNELS) { + flac__utils_printf(stderr, 1, "%s: ERROR: unsupported number of channels %u\n", encoder_session.inbasefilename, channels); return EncoderSession_finish_error(&encoder_session); } - else if(options.common.sector_align && x != 2) { - flac__utils_printf(stderr, 1, "%s: ERROR: file has %u channels, must be 2 for --sector-align\n", encoder_session.inbasefilename, (unsigned)x); + else if(options.common.sector_align && channels != 2) { + flac__utils_printf(stderr, 1, "%s: ERROR: file has %u channels, must be 2 for --sector-align\n", encoder_session.inbasefilename, channels); return EncoderSession_finish_error(&encoder_session); } - channels = x; /* sample rate */ if(!read_little_endian_uint32(infile, &xx, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); - if(!FLAC__format_sample_rate_is_valid(xx)) { - flac__utils_printf(stderr, 1, "%s: ERROR: unsupported sample rate %u\n", encoder_session.inbasefilename, (unsigned)xx); + sample_rate = xx; + if(!FLAC__format_sample_rate_is_valid(sample_rate)) { + flac__utils_printf(stderr, 1, "%s: ERROR: unsupported sample rate %u\n", encoder_session.inbasefilename, sample_rate); return EncoderSession_finish_error(&encoder_session); } - else if(options.common.sector_align && xx != 44100) { - flac__utils_printf(stderr, 1, "%s: ERROR: file's sample rate is %u, must be 44100 for --sector-align\n", encoder_session.inbasefilename, (unsigned)xx); + else if(options.common.sector_align && sample_rate != 44100) { + flac__utils_printf(stderr, 1, "%s: ERROR: file's sample rate is %u, must be 44100 for --sector-align\n", encoder_session.inbasefilename, sample_rate); return EncoderSession_finish_error(&encoder_session); } - sample_rate = xx; /* avg bytes per second (ignored) */ if(!read_little_endian_uint32(infile, &xx, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); /* block align */ if(!read_little_endian_uint16(infile, &x, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); - block_align = x; + block_align = (unsigned)x; /* bits per sample */ if(!read_little_endian_uint16(infile, &x, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); - bps = x; + bps = (unsigned)x; is_unsigned_samples = (bps <= 8); if(format == 1) { if(bps != 8 && bps != 16) { -#if 0 /* reinstate if we need to get stricter on the input */ if(bps == 24 || bps == 32) { -#endif /* let these slide with a warning since they're unambiguous */ flac__utils_printf(stderr, 1, "%s: WARNING: legacy WAVE file has format type %u but bits-per-sample=%u\n", encoder_session.inbasefilename, (unsigned)format, bps); -#if 0 /* reinstate if we need to get stricter on the input */ } else { + /* @@@ we could add an option to specify left- or right-justified blocks so we knew how to set 'shift' */ flac__utils_printf(stderr, 1, "%s: ERROR: legacy WAVE file has format type %u but bits-per-sample=%u\n", encoder_session.inbasefilename, (unsigned)format, bps); return EncoderSession_finish_error(&encoder_session); } -#endif } +#if 0 /* @@@ reinstate once we can get an answer about whether the samples are left- or right-justified */ if((bps+7)/8 * channels == block_align) { if(bps % 8) { - /* assume legacy file is block aligned with some LSBs zero; this is checked for in format_input() */ + /* assume legacy file is byte aligned with some LSBs zero; this is double-checked in format_input() */ flac__utils_printf(stderr, 1, "%s: WARNING: legacy WAVE file (format type %d) has block alignment=%u, bits-per-sample=%u, channels=%u\n", encoder_session.inbasefilename, (unsigned)format, block_align, bps, channels); shift = 8 - (bps % 8); bps += shift; @@ -689,26 +738,33 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con flac__utils_printf(stderr, 1, "%s: ERROR: illegal WAVE file (format type %d) has block alignment=%u, bits-per-sample=%u, channels=%u\n", encoder_session.inbasefilename, (unsigned)format, block_align, bps, channels); return EncoderSession_finish_error(&encoder_session); } +#else + shift = 0; +#endif + if(channels > 2 && !options.common.channel_map_none) { + flac__utils_printf(stderr, 1, "%s: ERROR: WAVE has >2 channels but is not WAVE_FORMAT_EXTENSIBLE; cannot assign channels\n", encoder_session.inbasefilename); + return EncoderSession_finish_error(&encoder_session); + } FLAC__ASSERT(data_bytes >= 16); data_bytes -= 16; } else { if(data_bytes < 40) { - flac__utils_printf(stderr, 1, "%s: ERROR: invalid WAVEFORMATEXTENSIBLE sub-chunk with size %u\n", encoder_session.inbasefilename, data_bytes); + flac__utils_printf(stderr, 1, "%s: ERROR: invalid WAVEFORMATEXTENSIBLE chunk with size %u\n", encoder_session.inbasefilename, data_bytes); return EncoderSession_finish_error(&encoder_session); } /* cbSize */ if(!read_little_endian_uint16(infile, &x, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); if(x < 22) { - flac__utils_printf(stderr, 1, "%s: ERROR: invalid WAVEFORMATEXTENSIBLE sub-chunk with cbSize %u\n", encoder_session.inbasefilename, (unsigned)x); + flac__utils_printf(stderr, 1, "%s: ERROR: invalid WAVEFORMATEXTENSIBLE chunk with cbSize %u\n", encoder_session.inbasefilename, (unsigned)x); return EncoderSession_finish_error(&encoder_session); } /* valid bps */ if(!read_little_endian_uint16(infile, &x, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); if((unsigned)x > bps) { - flac__utils_printf(stderr, 1, "%s: ERROR: invalid WAVEFORMATEXTENSIBLE sub-chunk with wValidBitsPerSample (%u) > wBitsPerSample (%u)\n", encoder_session.inbasefilename, (unsigned)x, bps); + flac__utils_printf(stderr, 1, "%s: ERROR: invalid WAVEFORMATEXTENSIBLE chunk with wValidBitsPerSample (%u) > wBitsPerSample (%u)\n", encoder_session.inbasefilename, (unsigned)x, bps); return EncoderSession_finish_error(&encoder_session); } shift = bps - (unsigned)x; @@ -722,21 +778,85 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con else if(channels == 2) channel_mask = 0x0003; } - if(channel_mask != 0x0001 && channel_mask != 0x0003 && channel_mask != 0x003f && channel_mask != 0x060f) { - flac__utils_printf(stderr, 1, "%s: ERROR: WAVEFORMATEXTENSIBLE sub-chunk with unsupported channel mask=0x%04X and #channels=%u\n", encoder_session.inbasefilename, (unsigned)channel_mask, channels); - return EncoderSession_finish_error(&encoder_session); + /* set channel mapping */ + /* FLAC order follows SMPTE and WAVEFORMATEXTENSIBLE but with fewer channels, which are: */ + /* front left, front right, center, LFE, back left, back right, surround left, surround right */ + /* the default mapping is sufficient for 1-6 channels and 7-8 are currently unspecified anyway */ +#if 0 + /* @@@ example for dolby/vorbis order, for reference later in case it becomes important */ + if( + options.common.channel_map_none || + channel_mask == 0x0001 || /* 1 channel: (mono) */ + channel_mask == 0x0003 || /* 2 channels: front left, front right */ + channel_mask == 0x0033 || /* 4 channels: front left, front right, back left, back right */ + channel_mask == 0x0603 /* 4 channels: front left, front right, side left, side right */ + ) { + /* keep default channel order */ + } + else if( + channel_mask == 0x0007 || /* 3 channels: front left, front right, front center */ + channel_mask == 0x0037 || /* 5 channels: front left, front right, front center, back left, back right */ + channel_mask == 0x0607 /* 5 channels: front left, front right, front center, side left, side right */ + ) { + /* to dolby order: front left, center, front right [, surround left, surround right ] */ + channel_map[1] = 2; + channel_map[2] = 1; + } + else if( + channel_mask == 0x003f || /* 6 channels: front left, front right, front center, LFE, back left, back right */ + channel_mask == 0x060f /* 6 channels: front left, front right, front center, LFE, side left, side right */ + ) { + /* to dolby order: front left, center, front right, surround left, surround right, LFE */ + channel_map[1] = 2; + channel_map[2] = 1; + channel_map[3] = 5; + channel_map[4] = 3; + channel_map[5] = 4; + } +#else + if( + options.common.channel_map_none || + channel_mask == 0x0001 || /* 1 channel: (mono) */ + channel_mask == 0x0003 || /* 2 channels: front left, front right */ + channel_mask == 0x0007 || /* 3 channels: front left, front right, front center */ + channel_mask == 0x0033 || /* 4 channels: front left, front right, back left, back right */ + channel_mask == 0x0603 || /* 4 channels: front left, front right, side left, side right */ + channel_mask == 0x0037 || /* 5 channels: front left, front right, front center, back left, back right */ + channel_mask == 0x0607 || /* 5 channels: front left, front right, front center, side left, side right */ + channel_mask == 0x003f || /* 6 channels: front left, front right, front center, LFE, back left, back right */ + channel_mask == 0x060f /* 6 channels: front left, front right, front center, LFE, side left, side right */ + ) { + /* keep default channel order */ } - if(count_channel_mask_bits(channel_mask) < channels) { - flac__utils_printf(stderr, 1, "%s: ERROR: WAVEFORMATEXTENSIBLE sub-chunk: channel mask 0x%04X has unassigned channels (#channels=%u)\n", encoder_session.inbasefilename, channel_mask, channels); +#endif + else { + flac__utils_printf(stderr, 1, "%s: ERROR: WAVEFORMATEXTENSIBLE chunk with unsupported channel mask=0x%04X\n", encoder_session.inbasefilename, (unsigned)channel_mask); return EncoderSession_finish_error(&encoder_session); } - else if(count_channel_mask_bits(channel_mask) > channels) - channel_mask = limit_channel_mask(channel_mask, channels); + if(!options.common.channel_map_none) { + if(count_channel_mask_bits(channel_mask) < channels) { + flac__utils_printf(stderr, 1, "%s: ERROR: WAVEFORMATEXTENSIBLE chunk: channel mask 0x%04X has unassigned channels (#channels=%u)\n", encoder_session.inbasefilename, (unsigned)channel_mask, channels); + return EncoderSession_finish_error(&encoder_session); + } +#if 0 + /* supporting this is too difficult with channel mapping; e.g. what if mask is 0x003f but #channels=4? + * there would be holes in the order that would have to be filled in, or the mask would have to be + * limited and the logic above rerun to see if it still fits into the FLAC mapping. + */ + else if(count_channel_mask_bits(channel_mask) > channels) + channel_mask = limit_channel_mask(channel_mask, channels); +#else + else if(count_channel_mask_bits(channel_mask) > channels) { + flac__utils_printf(stderr, 1, "%s: ERROR: WAVEFORMATEXTENSIBLE chunk: channel mask 0x%04X has extra bits for non-existant channels (#channels=%u)\n", encoder_session.inbasefilename, (unsigned)channel_mask, channels); + return EncoderSession_finish_error(&encoder_session); + } +#endif + } /* first part of GUID */ if(!read_little_endian_uint16(infile, &x, false, encoder_session.inbasefilename)) return EncoderSession_finish_error(&encoder_session); if(x != 1) { - flac__utils_printf(stderr, 1, "%s: ERROR: unsupported WAVEFORMATEXTENSIBLE sub-chunk with non-PCM format %u\n", encoder_session.inbasefilename, (unsigned)x); + flac__utils_printf(stderr, 1, "%s: ERROR: unsupported WAVEFORMATEXTENSIBLE chunk with non-PCM format %u\n", encoder_session.inbasefilename, (unsigned)x); return EncoderSession_finish_error(&encoder_session); } data_bytes -= 26; @@ -747,7 +867,7 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con return EncoderSession_finish_error(&encoder_session); } else if(options.common.sector_align && bps-shift != 16) { - flac__utils_printf(stderr, 1, "%s: ERROR: file has %u bits per sample, must be 16 for --sector-align\n", encoder_session.inbasefilename, bps-shift); + flac__utils_printf(stderr, 1, "%s: ERROR: file has %u bits-per-sample, must be 16 for --sector-align\n", encoder_session.inbasefilename, bps-shift); return EncoderSession_finish_error(&encoder_session); } @@ -821,7 +941,7 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con /* +44 for the size of the WAV 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 * bytes_per_wide_sample + 44; - if(!EncoderSession_init_encoder(&encoder_session, options.common, channels, bps-shift, sample_rate, /*flac_decoder_data=*/0)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, channel_mask, channels, bps-shift, sample_rate, /*flac_decoder_data=*/0)) return EncoderSession_finish_error(&encoder_session); /* @@ -869,7 +989,7 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con } else { unsigned wide_samples = bytes_read / bytes_per_wide_sample; - if(!format_input(input_, wide_samples, /*is_big_endian=*/false, is_unsigned_samples, channels, bps, shift)) + if(!format_input(input_, wide_samples, /*is_big_endian=*/false, is_unsigned_samples, channels, bps, shift, channel_map)) return EncoderSession_finish_error(&encoder_session); if(!EncoderSession_process(&encoder_session, (const FLAC__int32 * const *)input_, wide_samples)) { @@ -921,7 +1041,7 @@ int flac__encode_wav(FILE *infile, off_t infilesize, const char *infilename, con } else { info_align_carry = *options.common.align_reservoir_samples; - if(!format_input(options.common.align_reservoir, *options.common.align_reservoir_samples, /*is_big_endian=*/false, is_unsigned_samples, channels, bps, shift)) + if(!format_input(options.common.align_reservoir, *options.common.align_reservoir_samples, /*is_big_endian=*/false, is_unsigned_samples, channels, bps, shift, channel_map)) return EncoderSession_finish_error(&encoder_session); } } @@ -1072,7 +1192,7 @@ int flac__encode_raw(FILE *infile, off_t infilesize, const char *infilename, con } } - if(!EncoderSession_init_encoder(&encoder_session, options.common, options.channels, options.bps, options.sample_rate, /*flac_decoder_data=*/0)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, /*channel_mask=*/0, options.channels, options.bps, options.sample_rate, /*flac_decoder_data=*/0)) return EncoderSession_finish_error(&encoder_session); /* @@ -1130,7 +1250,7 @@ int flac__encode_raw(FILE *infile, off_t infilesize, const char *infilename, con } else { unsigned wide_samples = bytes_read / bytes_per_wide_sample; - if(!format_input(input_, wide_samples, options.is_big_endian, options.is_unsigned_samples, options.channels, options.bps, /*shift=*/0)) + if(!format_input(input_, wide_samples, options.is_big_endian, options.is_unsigned_samples, options.channels, options.bps, /*shift=*/0, /*channel_map=*/0)) return EncoderSession_finish_error(&encoder_session); if(!EncoderSession_process(&encoder_session, (const FLAC__int32 * const *)input_, wide_samples)) { @@ -1183,7 +1303,7 @@ int flac__encode_raw(FILE *infile, off_t infilesize, const char *infilename, con } else { unsigned wide_samples = bytes_read / bytes_per_wide_sample; - if(!format_input(input_, wide_samples, options.is_big_endian, options.is_unsigned_samples, options.channels, options.bps, /*shift=*/0)) + if(!format_input(input_, wide_samples, options.is_big_endian, options.is_unsigned_samples, options.channels, options.bps, /*shift=*/0, /*channel_map=*/0)) return EncoderSession_finish_error(&encoder_session); if(!EncoderSession_process(&encoder_session, (const FLAC__int32 * const *)input_, wide_samples)) { @@ -1228,7 +1348,7 @@ int flac__encode_raw(FILE *infile, off_t infilesize, const char *infilename, con } else { info_align_carry = *options.common.align_reservoir_samples; - if(!format_input(options.common.align_reservoir, *options.common.align_reservoir_samples, options.is_big_endian, options.is_unsigned_samples, options.channels, options.bps, /*shift=*/0)) + if(!format_input(options.common.align_reservoir, *options.common.align_reservoir_samples, options.is_big_endian, options.is_unsigned_samples, options.channels, options.bps, /*shift=*/0, /*channel_map=*/0)) return EncoderSession_finish_error(&encoder_session); } } @@ -1342,7 +1462,8 @@ int flac__encode_flac(FILE *infile, off_t infilesize, const char *infilename, co encoder_session.unencoded_size = decoder_data.filesize; - if(!EncoderSession_init_encoder(&encoder_session, options.common, decoder_data.metadata_blocks[0]->data.stream_info.channels, decoder_data.metadata_blocks[0]->data.stream_info.bits_per_sample, decoder_data.metadata_blocks[0]->data.stream_info.sample_rate, &decoder_data)) + /* (channel mask will get copied over from the source VORBIS_COMMENT if it exists) */ + if(!EncoderSession_init_encoder(&encoder_session, options.common, /*channel_mask=*/0, decoder_data.metadata_blocks[0]->data.stream_info.channels, decoder_data.metadata_blocks[0]->data.stream_info.bits_per_sample, decoder_data.metadata_blocks[0]->data.stream_info.sample_rate, &decoder_data)) return EncoderSession_finish_error(&encoder_session); /* @@ -1554,7 +1675,7 @@ int EncoderSession_finish_error(EncoderSession *e) return 1; } -FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, unsigned channels, unsigned bps, unsigned sample_rate, FLACDecoderData *flac_decoder_data) +FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, FLAC__uint32 channel_mask, unsigned channels, unsigned bps, unsigned sample_rate, FLACDecoderData *flac_decoder_data) { unsigned num_metadata, i; FLAC__StreamMetadata padding, *cuesheet = 0; @@ -1652,7 +1773,8 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio * next handle vorbis comment: if any tags were specified * or there is no existing vorbis comment, we create a * new vorbis comment (discarding any existing one); else - * we keep the existing one + * we keep the existing one. also need to make sure to + * propagate any channel mask tag. */ size_t i, j; FLAC__bool vc_found = false; @@ -1660,8 +1782,8 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) vc_found = true; if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_VORBIS_COMMENT && options.vorbis_comment->data.vorbis_comment.num_comments > 0) { - if(options.vorbis_comment->data.vorbis_comment.num_comments > 0) - flac__utils_printf(stderr, 1, "%s: WARNING, replacing tags from input FLAC file with those given on the command-line\n", e->inbasefilename); + (void) flac__utils_get_channel_mask_tag(flac_decoder_data->metadata_blocks[i], &channel_mask); + flac__utils_printf(stderr, 1, "%s: WARNING, replacing tags from input FLAC file with those given on the command-line\n", e->inbasefilename); FLAC__metadata_object_delete(flac_decoder_data->metadata_blocks[i]); flac_decoder_data->metadata_blocks[i] = 0; } @@ -1672,7 +1794,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio if((!vc_found || options.vorbis_comment->data.vorbis_comment.num_comments > 0) && flac_decoder_data->num_metadata_blocks < sizeof(flac_decoder_data->metadata_blocks)/sizeof(flac_decoder_data->metadata_blocks[0])) { /* prepend ours */ FLAC__StreamMetadata *vc = FLAC__metadata_object_clone(options.vorbis_comment); - if(0 == vc) { + if(0 == vc || (channel_mask && !flac__utils_set_channel_mask_tag(vc, channel_mask))) { flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for VORBIS_COMMENT block\n", e->inbasefilename); if(0 != cuesheet) FLAC__metadata_object_delete(cuesheet); @@ -1793,6 +1915,14 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio } if(0 != cuesheet) metadata[num_metadata++] = cuesheet; + if(channel_mask) { + if(!flac__utils_set_channel_mask_tag(options.vorbis_comment, channel_mask)) { + flac__utils_printf(stderr, 1, "%s: ERROR adding channel mask tag\n", e->inbasefilename); + if(0 != cuesheet) + FLAC__metadata_object_delete(cuesheet); + return false; + } + } metadata[num_metadata++] = options.vorbis_comment; for(i = 0; i < options.num_pictures; i++) metadata[num_metadata++] = options.pictures[i]; @@ -2046,20 +2176,30 @@ FLAC__bool verify_metadata(const EncoderSession *e, FLAC__StreamMetadata **metad return true; } -FLAC__bool format_input(FLAC__int32 *dest[], unsigned wide_samples, FLAC__bool is_big_endian, FLAC__bool is_unsigned_samples, unsigned channels, unsigned bps, unsigned shift) +FLAC__bool format_input(FLAC__int32 *dest[], unsigned wide_samples, FLAC__bool is_big_endian, FLAC__bool is_unsigned_samples, unsigned channels, unsigned bps, unsigned shift, size_t *channel_map) { unsigned wide_sample, sample, channel, byte; + FLAC__int32 *out[FLAC__MAX_CHANNELS]; + + if(0 == channel_map) { + for(channel = 0; channel < channels; channel++) + out[channel] = dest[channel]; + } + else { + for(channel = 0; channel < channels; channel++) + out[channel] = dest[channel_map[channel]]; + } if(bps == 8) { if(is_unsigned_samples) { for(sample = wide_sample = 0; wide_sample < wide_samples; wide_sample++) for(channel = 0; channel < channels; channel++, sample++) - dest[channel][wide_sample] = (FLAC__int32)ucbuffer_[sample] - 0x80; + out[channel][wide_sample] = (FLAC__int32)ucbuffer_[sample] - 0x80; } else { for(sample = wide_sample = 0; wide_sample < wide_samples; wide_sample++) for(channel = 0; channel < channels; channel++, sample++) - dest[channel][wide_sample] = (FLAC__int32)scbuffer_[sample]; + out[channel][wide_sample] = (FLAC__int32)scbuffer_[sample]; } } else if(bps == 16) { @@ -2075,12 +2215,12 @@ FLAC__bool format_input(FLAC__int32 *dest[], unsigned wide_samples, FLAC__bool i if(is_unsigned_samples) { for(sample = wide_sample = 0; wide_sample < wide_samples; wide_sample++) for(channel = 0; channel < channels; channel++, sample++) - dest[channel][wide_sample] = (FLAC__int32)usbuffer_[sample] - 0x8000; + out[channel][wide_sample] = (FLAC__int32)usbuffer_[sample] - 0x8000; } else { for(sample = wide_sample = 0; wide_sample < wide_samples; wide_sample++) for(channel = 0; channel < channels; channel++, sample++) - dest[channel][wide_sample] = (FLAC__int32)ssbuffer_[sample]; + out[channel][wide_sample] = (FLAC__int32)ssbuffer_[sample]; } } else if(bps == 24) { @@ -2096,18 +2236,18 @@ FLAC__bool format_input(FLAC__int32 *dest[], unsigned wide_samples, FLAC__bool i if(is_unsigned_samples) { for(byte = sample = wide_sample = 0; wide_sample < wide_samples; wide_sample++) for(channel = 0; channel < channels; channel++, sample++) { - dest[channel][wide_sample] = ucbuffer_[byte++]; dest[channel][wide_sample] <<= 8; - dest[channel][wide_sample] |= ucbuffer_[byte++]; dest[channel][wide_sample] <<= 8; - dest[channel][wide_sample] |= ucbuffer_[byte++]; - dest[channel][wide_sample] -= 0x800000; + out[channel][wide_sample] = ucbuffer_[byte++]; out[channel][wide_sample] <<= 8; + out[channel][wide_sample] |= ucbuffer_[byte++]; out[channel][wide_sample] <<= 8; + out[channel][wide_sample] |= ucbuffer_[byte++]; + out[channel][wide_sample] -= 0x800000; } } else { for(byte = sample = wide_sample = 0; wide_sample < wide_samples; wide_sample++) for(channel = 0; channel < channels; channel++, sample++) { - dest[channel][wide_sample] = scbuffer_[byte++]; dest[channel][wide_sample] <<= 8; - dest[channel][wide_sample] |= ucbuffer_[byte++]; dest[channel][wide_sample] <<= 8; - dest[channel][wide_sample] |= ucbuffer_[byte++]; + out[channel][wide_sample] = scbuffer_[byte++]; out[channel][wide_sample] <<= 8; + out[channel][wide_sample] |= ucbuffer_[byte++]; out[channel][wide_sample] <<= 8; + out[channel][wide_sample] |= ucbuffer_[byte++]; } } } @@ -2118,11 +2258,11 @@ FLAC__bool format_input(FLAC__int32 *dest[], unsigned wide_samples, FLAC__bool i FLAC__int32 mask = (1<>= shift; + out[channel][wide_sample] >>= shift; } } return true; diff --git a/src/flac/encode.h b/src/flac/encode.h index 95a570c3..1450253a 100644 --- a/src/flac/encode.h +++ b/src/flac/encode.h @@ -59,6 +59,7 @@ typedef struct { int num_requested_seek_points; const char *cuesheet_filename; FLAC__bool cued_seekpoints; + FLAC__bool channel_map_none; /* --channel-map=none specified, eventually will expand to take actual channel map */ /* options related to --replay-gain and --sector-align */ FLAC__bool is_first_file; diff --git a/src/flac/main.c b/src/flac/main.c index 1666c17e..4c9a4a6a 100644 --- a/src/flac/main.c +++ b/src/flac/main.c @@ -106,6 +106,7 @@ static struct share__option long_options_[] = { { "output-name" , share__required_argument, 0, 'o' }, { "skip" , share__required_argument, 0, 0 }, { "until" , share__required_argument, 0, 0 }, + { "channel-map" , share__required_argument, 0, 0 }, /* undocumented */ /* * decoding options @@ -255,6 +256,7 @@ static struct { int num_requested_seek_points; /* -1 => no -S options were given, 0 => -S- was given */ const char *cuesheet_filename; FLAC__bool cued_seekpoints; + FLAC__bool channel_map_none; /* --channel-map=none specified, eventually will expand to take actual channel map */ unsigned num_files; char **filenames; @@ -604,6 +606,7 @@ FLAC__bool init_options() option_values.num_requested_seek_points = -1; option_values.cuesheet_filename = 0; option_values.cued_seekpoints = true; + option_values.channel_map_none = false;; option_values.num_files = 0; option_values.filenames = 0; @@ -734,6 +737,11 @@ int parse_option(int short_option, const char *long_option, const char *option_a } } } + else if(0 == strcmp(long_option, "channel-map")) { + if (0 == option_argument || strcmp(option_argument, "none")) + return usage_error("ERROR: only --channel-map=none currently supported\n"); + option_values.channel_map_none = true; + } else if(0 == strcmp(long_option, "cuesheet")) { FLAC__ASSERT(0 != option_argument); option_values.cuesheet_filename = option_argument; @@ -1752,6 +1760,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ common_options.num_requested_seek_points = option_values.num_requested_seek_points; common_options.cuesheet_filename = option_values.cuesheet_filename; common_options.cued_seekpoints = option_values.cued_seekpoints; + common_options.channel_map_none = option_values.channel_map_none; common_options.is_first_file = is_first_file; common_options.is_last_file = is_last_file; common_options.align_reservoir = align_reservoir; @@ -1919,6 +1928,7 @@ int decode_file(const char *infilename) common_options.use_first_serial_number = !option_values.has_serial_number; common_options.serial_number = option_values.serial_number; #endif + common_options.channel_map_none = option_values.channel_map_none; if(!option_values.force_raw_format) { wav_decode_options_t options; diff --git a/src/flac/utils.c b/src/flac/utils.c index 50e672ab..401f511e 100644 --- a/src/flac/utils.c +++ b/src/flac/utils.c @@ -22,11 +22,14 @@ #include "utils.h" #include "FLAC/assert.h" +#include "FLAC/metadata.h" #include #include #include #include +const char *CHANNEL_MASK_TAG = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK"; + int flac__utils_verbosity_ = 2; static FLAC__bool local__parse_uint64_(const char *s, FLAC__uint64 *value) @@ -269,3 +272,40 @@ void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *c else until_spec->value.samples = total_samples; } + +FLAC__bool flac__utils_set_channel_mask_tag(FLAC__StreamMetadata *object, FLAC__uint32 channel_mask) +{ + FLAC__StreamMetadata_VorbisComment_Entry entry = { 0, 0 }; + char tag[128]; + + FLAC__ASSERT(object); + FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__ASSERT(strlen(CHANNEL_MASK_TAG+1+2+16+1) <= sizeof(tag)); /* +1 for =, +2 for 0x, +16 for digits, +1 for NUL */ + entry.entry = (FLAC__byte*)tag; + if((entry.length = snprintf(tag, sizeof(tag), "%s=0x%04X", CHANNEL_MASK_TAG, (unsigned)channel_mask)) >= sizeof(tag)) + return false; + if(!FLAC__metadata_object_vorbiscomment_replace_comment(object, entry, /*all=*/true, /*copy=*/true)) + return false; + return true; +} + +FLAC__bool flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata *object, FLAC__uint32 *channel_mask) +{ + int offset; + unsigned val; + char *p; + FLAC__ASSERT(object); + FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + if(0 > (offset = FLAC__metadata_object_vorbiscomment_find_entry_from(object, /*offset=*/0, CHANNEL_MASK_TAG))) + return false; + if(object->data.vorbis_comment.comments[offset].length < strlen(CHANNEL_MASK_TAG)+4) + return false; + if(0 == (p = strchr((const char *)object->data.vorbis_comment.comments[offset].entry, '='))) /* should never happen, but just in case */ + return false; + if(strncmp(p, "=0x", 3)) + return false; + if(sscanf(p+3, "%x", &val) != 1) + return false; + *channel_mask = val; + return true; +} diff --git a/src/flac/utils.h b/src/flac/utils.h index 4fb61c8d..de8e6b31 100644 --- a/src/flac/utils.h +++ b/src/flac/utils.h @@ -53,4 +53,7 @@ void flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecifica FLAC__bool flac__utils_parse_cue_specification(const char *s, utils__CueSpecification *spec); void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *cue_spec, const FLAC__StreamMetadata_CueSheet *cuesheet, FLAC__uint64 total_samples, utils__SkipUntilSpecification *skip_spec, utils__SkipUntilSpecification *until_spec); +FLAC__bool flac__utils_set_channel_mask_tag(FLAC__StreamMetadata *object, FLAC__uint32 channel_mask); +FLAC__bool flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata *object, FLAC__uint32 *channel_mask); + #endif diff --git a/src/test_streams/main.c b/src/test_streams/main.c index 1780a584..6685ecf0 100644 --- a/src/test_streams/main.c +++ b/src/test_streams/main.c @@ -614,8 +614,9 @@ foo: return false; } -static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsigned channels, unsigned bytes_per_sample, unsigned samples) +static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsigned channels, unsigned bytes_per_sample, unsigned samples, FLAC__bool strict) { + const FLAC__bool waveformatextensible = strict && channels > 2; const unsigned true_size = channels * bytes_per_sample * samples; const unsigned padded_size = (true_size + 1) & (~1u); FILE *f; @@ -625,9 +626,13 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig return false; if(fwrite("RIFF", 1, 4, f) < 4) goto foo; - if(!write_little_endian_uint32(f, padded_size + 36)) + if(!write_little_endian_uint32(f, padded_size + (waveformatextensible?60:36))) goto foo; - if(fwrite("WAVEfmt \020\000\000\000\001\000", 1, 14, f) < 14) + if(fwrite("WAVEfmt ", 1, 8, f) < 8) + goto foo; + if(!write_little_endian_uint32(f, waveformatextensible?40:16)) + goto foo; + if(!write_little_endian_uint16(f, waveformatextensible?65534:1)) goto foo; if(!write_little_endian_uint16(f, (FLAC__uint16)channels)) goto foo; @@ -639,6 +644,17 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig goto foo; if(!write_little_endian_uint16(f, (FLAC__uint16)(8 * bytes_per_sample))) goto foo; + if(waveformatextensible) { + if(!write_little_endian_uint16(f, (FLAC__uint16)22)) /* cbSize */ + goto foo; + if(!write_little_endian_uint16(f, (FLAC__uint16)(8 * bytes_per_sample))) /* validBitsPerSample */ + goto foo; + if(!write_little_endian_uint32(f, 0)) /* channelMask */ + goto foo; + /* GUID = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}} */ + if(fwrite("\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71", 1, 16, f) != 16) + goto foo; + } if(fwrite("data", 1, 4, f) < 4) goto foo; if(!write_little_endian_uint32(f, true_size)) @@ -815,7 +831,7 @@ int main(int argc, char *argv[]) return 1; sprintf(fn, "rt-%u-%u-%u.wav", channels, bytes_per_sample, nsamples[samples]); - if(!generate_wav(fn, 44100, channels, bytes_per_sample, nsamples[samples])) + if(!generate_wav(fn, 44100, channels, bytes_per_sample, nsamples[samples], /*strict=*/true)) return 1; } } diff --git a/test/test_flac.sh b/test/test_flac.sh index 6bf71b9e..c4d11800 100755 --- a/test/test_flac.sh +++ b/test/test_flac.sh @@ -159,9 +159,9 @@ rt_test_wav () { f="$1" echo -n "round-trip test ($f) encode... " - run_flac $SILENT --force --verify $f -o rt.flac || die "ERROR" + run_flac $SILENT --force --verify --channel-map=none $f -o rt.flac || die "ERROR" echo -n "decode... " - run_flac $SILENT --force --decode -o rt.wav rt.flac || die "ERROR" + run_flac $SILENT --force --decode --channel-map=none -o rt.wav rt.flac || die "ERROR" echo -n "compare... " cmp $f rt.wav || die "ERROR: file mismatch" echo "OK" @@ -172,9 +172,9 @@ rt_test_aiff () { f="$1" echo -n "round-trip test ($f) encode... " - run_flac $SILENT --force --verify $f -o rt.flac || die "ERROR" + run_flac $SILENT --force --verify --channel-map=none $f -o rt.flac || die "ERROR" echo -n "decode... " - run_flac $SILENT --force --decode -o rt.aiff rt.flac || die "ERROR" + run_flac $SILENT --force --decode --channel-map=none -o rt.aiff rt.flac || die "ERROR" echo -n "compare... " cmp $f rt.aiff || die "ERROR: file mismatch" echo "OK" @@ -186,11 +186,11 @@ rt_test_flac () { f="$1" echo -n "round-trip test ($f->flac->flac->wav) encode... " - run_flac $SILENT --force --verify $f -o rt.flac || die "ERROR" + run_flac $SILENT --force --verify --channel-map=none $f -o rt.flac || die "ERROR" echo -n "re-encode... " run_flac $SILENT --force --verify -o rt2.flac rt.flac || die "ERROR" echo -n "decode... " - run_flac $SILENT --force --decode -o rt.wav rt2.flac || die "ERROR" + run_flac $SILENT --force --decode --channel-map=none -o rt.wav rt2.flac || die "ERROR" echo -n "compare... " cmp $f rt.wav || die "ERROR: file mismatch" echo "OK" -- GitLab