Commit cc5c1d88 authored by Josh Coalson's avatar Josh Coalson

complete WAVEFORMATEXTENSIBLE support, multichannel assignments in format and...

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
parent b73fff6e
...@@ -54,6 +54,7 @@ typedef struct { ...@@ -54,6 +54,7 @@ typedef struct {
FLAC__bool is_aiff_out; FLAC__bool is_aiff_out;
FLAC__bool is_wave_out; FLAC__bool is_wave_out;
FLAC__bool continue_through_decode_errors; FLAC__bool continue_through_decode_errors;
FLAC__bool channel_map_none;
struct { struct {
replaygain_synthesis_spec_t spec; replaygain_synthesis_spec_t spec;
...@@ -87,6 +88,7 @@ typedef struct { ...@@ -87,6 +88,7 @@ typedef struct {
unsigned bps; unsigned bps;
unsigned channels; unsigned channels;
unsigned sample_rate; unsigned sample_rate;
FLAC__uint32 channel_mask;
union { union {
FLAC__StreamDecoder *flac; FLAC__StreamDecoder *flac;
...@@ -105,7 +107,7 @@ static FLAC__bool is_big_endian_host_; ...@@ -105,7 +107,7 @@ static FLAC__bool is_big_endian_host_;
/* /*
* local routines * 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 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_init_decoder(DecoderSession *d, decode_options_t decode_options, const char *infilename);
static FLAC__bool DecoderSession_process(DecoderSession *d); static FLAC__bool DecoderSession_process(DecoderSession *d);
...@@ -145,6 +147,7 @@ int flac__decode_aiff(const char *infilename, const char *outfilename, FLAC__boo ...@@ -145,6 +147,7 @@ int flac__decode_aiff(const char *infilename, const char *outfilename, FLAC__boo
/*is_aiff_out=*/true, /*is_aiff_out=*/true,
/*is_wave_out=*/false, /*is_wave_out=*/false,
options.common.continue_through_decode_errors, options.common.continue_through_decode_errors,
options.common.channel_map_none,
options.common.replaygain_synthesis_spec, options.common.replaygain_synthesis_spec,
analysis_mode, analysis_mode,
aopts, aopts,
...@@ -181,6 +184,7 @@ int flac__decode_wav(const char *infilename, const char *outfilename, FLAC__bool ...@@ -181,6 +184,7 @@ int flac__decode_wav(const char *infilename, const char *outfilename, FLAC__bool
/*is_aiff_out=*/false, /*is_aiff_out=*/false,
/*is_wave_out=*/true, /*is_wave_out=*/true,
options.common.continue_through_decode_errors, options.common.continue_through_decode_errors,
options.common.channel_map_none,
options.common.replaygain_synthesis_spec, options.common.replaygain_synthesis_spec,
analysis_mode, analysis_mode,
aopts, aopts,
...@@ -220,6 +224,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool ...@@ -220,6 +224,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool
/*is_aiff_out=*/false, /*is_aiff_out=*/false,
/*is_wave_out=*/false, /*is_wave_out=*/false,
options.common.continue_through_decode_errors, options.common.continue_through_decode_errors,
options.common.channel_map_none,
options.common.replaygain_synthesis_spec, options.common.replaygain_synthesis_spec,
analysis_mode, analysis_mode,
aopts, aopts,
...@@ -241,7 +246,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool ...@@ -241,7 +246,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool
return DecoderSession_finish_ok(&decoder_session); 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 #ifdef FLAC__HAS_OGG
d->is_ogg = is_ogg; d->is_ogg = is_ogg;
...@@ -252,6 +257,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__ ...@@ -252,6 +257,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__
d->is_aiff_out = is_aiff_out; d->is_aiff_out = is_aiff_out;
d->is_wave_out = is_wave_out; d->is_wave_out = is_wave_out;
d->continue_through_decode_errors = continue_through_decode_errors; d->continue_through_decode_errors = continue_through_decode_errors;
d->channel_map_none = channel_map_none;
d->replaygain.spec = replaygain_synthesis_spec; d->replaygain.spec = replaygain_synthesis_spec;
d->replaygain.apply = false; d->replaygain.apply = false;
d->replaygain.scale = 0.0; d->replaygain.scale = 0.0;
...@@ -279,6 +285,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__ ...@@ -279,6 +285,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__
d->bps = 0; d->bps = 0;
d->channels = 0; d->channels = 0;
d->sample_rate = 0; d->sample_rate = 0;
d->channel_mask = 0;
d->decoder.flac = 0; d->decoder.flac = 0;
#ifdef FLAC__HAS_OGG #ifdef FLAC__HAS_OGG
...@@ -409,6 +416,36 @@ FLAC__bool DecoderSession_process(DecoderSession *d) ...@@ -409,6 +416,36 @@ FLAC__bool DecoderSession_process(DecoderSession *d)
if(d->abort_flag) if(d->abort_flag)
return false; 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 */ /* 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->is_wave_out || d->is_aiff_out)) {
if(!write_iff_headers(d->fout, d, d->total_samples)) { if(!write_iff_headers(d->fout, d, d->total_samples)) {
...@@ -610,6 +647,7 @@ FLAC__bool canonicalize_until_specification(utils__SkipUntilSpecification *spec, ...@@ -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) 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 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); 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 FLAC__uint32 aligned_data_size = (FLAC__uint32)((data_size+1) & (~1U)); /* we'll check for overflow later */
if(samples == 0) { if(samples == 0) {
...@@ -630,16 +668,16 @@ FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uin ...@@ -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) if(flac__utils_fwrite("RIFF", 1, 4, f) != 4)
return false; 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; return false;
if(flac__utils_fwrite("WAVEfmt ", 1, 8, f) != 8) if(flac__utils_fwrite("WAVEfmt ", 1, 8, f) != 8)
return false; 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; 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; return false;
if(!write_little_endian_uint16(f, (FLAC__uint16)(decoder_session->channels))) 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 ...@@ -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 */ if(!write_little_endian_uint16(f, (FLAC__uint16)(decoder_session->channels * ((decoder_session->bps+7) / 8)))) /* block align */
return false; 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; 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) if(flac__utils_fwrite("data", 1, 4, f) != 4)
return false; return false;
...@@ -801,6 +854,7 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder ...@@ -801,6 +854,7 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
DecoderSession *decoder_session = (DecoderSession*)client_data; DecoderSession *decoder_session = (DecoderSession*)client_data;
FILE *fout = decoder_session->fout; FILE *fout = decoder_session->fout;
const unsigned bps = frame->header.bits_per_sample, channels = frame->header.channels; 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_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)); 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; unsigned wide_samples = frame->header.blocksize, wide_sample, sample, channel, byte;
...@@ -916,7 +970,12 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder ...@@ -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); flac__analyze_frame(frame, decoder_session->frame_counter-1, decoder_session->aopts, fout);
} }
else if(!decoder_session->test_only) { 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( bytes_to_write = FLAC__replaygain_synthesis__apply_gain(
u8buffer, u8buffer,
!is_big_endian, !is_big_endian,
...@@ -925,7 +984,7 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder ...@@ -925,7 +984,7 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
wide_samples, wide_samples,
channels, channels,
bps, /* source_bps */ bps, /* source_bps */
bps, /* target_bps */ bps+shift, /* target_bps */
decoder_session->replaygain.scale, decoder_session->replaygain.scale,
decoder_session->replaygain.spec.limiter == RGSS_LIMIT__HARD, /* hard_limit */ decoder_session->replaygain.spec.limiter == RGSS_LIMIT__HARD, /* hard_limit */
decoder_session->replaygain.spec.noise_shaping != NOISE_SHAPING_NONE, /* do_dithering */ decoder_session->replaygain.spec.noise_shaping != NOISE_SHAPING_NONE, /* do_dithering */
...@@ -1014,6 +1073,9 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder ...@@ -1014,6 +1073,9 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
} }
else { else {
FLAC__ASSERT(0); 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 ...@@ -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); 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);
} }
} }
......
...@@ -47,6 +47,7 @@ typedef struct { ...@@ -47,6 +47,7 @@ typedef struct {
utils__SkipUntilSpecification until_specification; utils__SkipUntilSpecification until_specification;
FLAC__bool has_cue_specification; FLAC__bool has_cue_specification;
utils__CueSpecification 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; } decode_options_t;
/* used for AIFF also */ /* used for AIFF also */
......
This diff is collapsed.
...@@ -59,6 +59,7 @@ typedef struct { ...@@ -59,6 +59,7 @@ typedef struct {
int num_requested_seek_points; int num_requested_seek_points;
const char *cuesheet_filename; const char *cuesheet_filename;
FLAC__bool cued_seekpoints; 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 */ /* options related to --replay-gain and --sector-align */
FLAC__bool is_first_file; FLAC__bool is_first_file;
......
...@@ -106,6 +106,7 @@ static struct share__option long_options_[] = { ...@@ -106,6 +106,7 @@ static struct share__option long_options_[] = {
{ "output-name" , share__required_argument, 0, 'o' }, { "output-name" , share__required_argument, 0, 'o' },
{ "skip" , share__required_argument, 0, 0 }, { "skip" , share__required_argument, 0, 0 },
{ "until" , share__required_argument, 0, 0 }, { "until" , share__required_argument, 0, 0 },
{ "channel-map" , share__required_argument, 0, 0 }, /* undocumented */
/* /*
* decoding options * decoding options
...@@ -255,6 +256,7 @@ static struct { ...@@ -255,6 +256,7 @@ static struct {
int num_requested_seek_points; /* -1 => no -S options were given, 0 => -S- was given */ int num_requested_seek_points; /* -1 => no -S options were given, 0 => -S- was given */
const char *cuesheet_filename; const char *cuesheet_filename;
FLAC__bool cued_seekpoints; FLAC__bool cued_seekpoints;
FLAC__bool channel_map_none; /* --channel-map=none specified, eventually will expand to take actual channel map */
unsigned num_files; unsigned num_files;
char **filenames; char **filenames;
...@@ -604,6 +606,7 @@ FLAC__bool init_options() ...@@ -604,6 +606,7 @@ FLAC__bool init_options()
option_values.num_requested_seek_points = -1; option_values.num_requested_seek_points = -1;
option_values.cuesheet_filename = 0; option_values.cuesheet_filename = 0;
option_values.cued_seekpoints = true; option_values.cued_seekpoints = true;
option_values.channel_map_none = false;;
option_values.num_files = 0; option_values.num_files = 0;
option_values.filenames = 0; option_values.filenames = 0;
...@@ -734,6 +737,11 @@ int parse_option(int short_option, const char *long_option, const char *option_a ...@@ -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")) { else if(0 == strcmp(long_option, "cuesheet")) {
FLAC__ASSERT(0 != option_argument); FLAC__ASSERT(0 != option_argument);
option_values.cuesheet_filename = 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_ ...@@ -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.num_requested_seek_points = option_values.num_requested_seek_points;
common_options.cuesheet_filename = option_values.cuesheet_filename; common_options.cuesheet_filename = option_values.cuesheet_filename;
common_options.cued_seekpoints = option_values.cued_seekpoints; 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_first_file = is_first_file;
common_options.is_last_file = is_last_file; common_options.is_last_file = is_last_file;
common_options.align_reservoir = align_reservoir; common_options.align_reservoir = align_reservoir;
...@@ -1919,6 +1928,7 @@ int decode_file(const char *infilename) ...@@ -1919,6 +1928,7 @@ int decode_file(const char *infilename)
common_options.use_first_serial_number = !option_values.has_serial_number; common_options.use_first_serial_number = !option_values.has_serial_number;
common_options.serial_number = option_values.serial_number; common_options.serial_number = option_values.serial_number;
#endif #endif
common_options.channel_map_none = option_values.channel_map_none;
if(!option_values.force_raw_format) { if(!option_values.force_raw_format) {
wav_decode_options_t options; wav_decode_options_t options;
......
...@@ -22,11 +22,14 @@ ...@@ -22,11 +22,14 @@
#include "utils.h" #include "utils.h"
#include "FLAC/assert.h" #include "FLAC/assert.h"
#include "FLAC/metadata.h"
#include <math.h> #include <math.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
const char *CHANNEL_MASK_TAG = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK";
int flac__utils_verbosity_ = 2; int flac__utils_verbosity_ = 2;
static FLAC__bool local__parse_uint64_(const char *s, FLAC__uint64 *value) 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 ...@@ -269,3 +272,40 @@ void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *c
else else
until_spec->value.samples = total_samples; 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;
}
...@@ -53,4 +53,7 @@ void flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecifica ...@@ -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); 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); 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 #endif
...@@ -614,8 +614,9 @@ foo: ...@@ -614,8 +614,9 @@ foo:
return false; 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 true_size = channels * bytes_per_sample * samples;
const unsigned padded_size = (true_size + 1) & (~1u); const unsigned padded_size = (true_size + 1) & (~1u);
FILE *f; FILE *f;
...@@ -625,9 +626,13 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig ...@@ -625,9 +626,13 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig
return false; return false;
if(fwrite("RIFF", 1, 4, f) < 4) if(fwrite("RIFF", 1, 4, f) < 4)
goto foo; goto foo;
if(!write_little_endian_uint32(f, padded_size + 36)) if(!write_little_endian_uint32(f, padded_size + (waveformatextensible?60:36)))
goto foo; 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; goto foo;
if(!write_little_endian_uint16(f, (FLAC__uint16)channels)) if(!write_little_endian_uint16(f, (FLAC__uint16)channels))
goto foo; goto foo;
...@@ -639,6 +644,17 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig ...@@ -639,6 +644,17 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig
goto foo; goto foo;
if(!write_little_endian_uint16(f, (FLAC__uint16)(8 * bytes_per_sample))) if(!write_little_endian_uint16(f, (FLAC__uint16)(8 * bytes_per_sample)))
goto foo; 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) if(fwrite("data", 1, 4, f) < 4)
goto foo; goto foo;
if(!write_little_endian_uint32(f, true_size)) if(!write_little_endian_uint32(f, true_size))
...@@ -815,7 +831,7 @@ int main(int argc, char *argv[]) ...@@ -815,7 +831,7 @@ int main(int argc, char *argv[])
return 1; return 1;
sprintf(fn, "rt-%u-%u-%u.wav", channels, bytes_per_sample, nsamples[samples]); 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; return 1;
} }
} }
......
...@@ -159,9 +159,9 @@ rt_test_wav () ...@@ -159,9 +159,9 @@ rt_test_wav ()
{ {
f="$1" f="$1"
echo -n "round-trip test ($f) encode... " 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... " 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... " echo -n "compare... "
cmp $f rt.wav || die "ERROR: file mismatch" cmp $f rt.wav || die "ERROR: file mismatch"
echo "OK" echo "OK"
...@@ -172,9 +172,9 @@ rt_test_aiff () ...@@ -172,9 +172,9 @@ rt_test_aiff ()
{ {
f="$1" f="$1"
echo -n "round-trip test ($f) encode... " 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... " 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... " echo -n "compare... "
cmp $f rt.aiff || die "ERROR: file mismatch" cmp $f rt.aiff || die "ERROR: file mismatch"
echo "OK" echo "OK"
...@@ -186,11 +186,11 @@ rt_test_flac () ...@@ -186,11 +186,11 @@ rt_test_flac ()
{ {
f="$1" f="$1"
echo -n "round-trip test ($f->flac->flac->wav) encode... " 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... " echo -n "re-encode... "