diff --git a/src/flac/encode.c b/src/flac/encode.c index cd71c163..104500f0 100644 --- a/src/flac/encode.c +++ b/src/flac/encode.c @@ -29,6 +29,7 @@ #include /* for malloc */ #include /* for strcmp() */ #include "FLAC/all.h" +#include "share/replaygain.h" #include "encode.h" #include "file.h" @@ -58,6 +59,10 @@ typedef struct { const char *inbasefilename; const char *outfilename; + FLAC__bool replay_gain; + unsigned channels; + unsigned bits_per_sample; + unsigned sample_rate; FLAC__uint64 unencoded_size; FLAC__uint64 total_samples_to_encode; FLAC__uint64 bytes_written; @@ -783,6 +788,9 @@ int flac__encode_raw(FILE *infile, long infilesize, const char *infilename, cons FLAC__ASSERT(!options.common.sector_align || options.bps == 16); FLAC__ASSERT(!options.common.sector_align || options.sample_rate == 44100); FLAC__ASSERT(!options.common.sector_align || infilesize >= 0); + FLAC__ASSERT(!options.common.replay_gain || options.common.skip == 0); + FLAC__ASSERT(!options.common.replay_gain || options.channels <= 2); + FLAC__ASSERT(!options.common.replay_gain || FLAC__replaygain_is_valid_sample_frequency(options.sample_rate)); if(! EncoderSession_construct( @@ -1174,6 +1182,22 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio FLAC__StreamMetadata padding; FLAC__StreamMetadata *metadata[3]; + e->replay_gain = option.common.replay_gain; + e->channels = channels; + e->bits_per_sample = bps; + e->sample_rate = sample_rate; + + if(e->replay_gain) { + if(channels != 1 && channels != 2) { + fprintf(stderr, "%s: ERROR, number of channels (%u) must be 1 or 2 for --replay-gain\n", e->inbasefilename, channels); + return false; + } + if(!FLAC__replaygain_is_valid_sample_frequency(sample_rate)) { + fprintf(stderr, "%s: ERROR, invalid sample rate (%u) for --replay-gain\n", e->inbasefilename, sample_rate); + return false; + } + } + if(channels != 2) options.do_mid_side = options.loose_mid_side = false; @@ -1304,6 +1328,11 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio FLAC__bool EncoderSession_process(EncoderSession *e, const FLAC__int32 * const buffer[], unsigned samples) { + if(e->replay_gain) { + if(!FLAC__replaygain_analyze(buffer, e->channels==2, e->bits_per_sample, samples)) + fprintf(stderr, "%s: WARNING, error while calculating ReplayGain\n", e->inbasefilename); + } + #ifdef FLAC__HAS_OGG if(e->use_ogg) { return OggFLAC__stream_encoder_process(e->encoder.ogg.stream, buffer, samples); diff --git a/src/flac/encode.h b/src/flac/encode.h index 0ed37cf6..a74dbb59 100644 --- a/src/flac/encode.h +++ b/src/flac/encode.h @@ -50,10 +50,12 @@ typedef struct { char *requested_seek_points; int num_requested_seek_points; - /* options related to --sector-align */ + /* options related to --replay-gain and --sector-align */ + FLAC__bool is_first_file; FLAC__bool is_last_file; FLAC__int32 **align_reservoir; unsigned *align_reservoir_samples; + FLAC__bool replay_gain; FLAC__bool sector_align; FLAC__StreamMetadata *vorbis_comment; diff --git a/src/flac/main.c b/src/flac/main.c index b8bff525..d9fc94f7 100644 --- a/src/flac/main.c +++ b/src/flac/main.c @@ -34,6 +34,7 @@ #define strcasecmp stricmp #endif #include "FLAC/all.h" +#include "share/replaygain.h" #include "analyze.h" #include "decode.h" #include "encode.h" @@ -64,7 +65,7 @@ static void show_help(); static void show_explain(); static void format_mistake(const char *infilename, const char *wrong, const char *right); -static int encode_file(const char *infilename, const char *forced_outfilename, FLAC__bool is_last_file); +static int encode_file(const char *infilename, const char *forced_outfilename, FLAC__bool is_first_file, FLAC__bool is_last_file); static int decode_file(const char *infilename, const char *forced_outfilename); static void die(const char *message); @@ -117,6 +118,7 @@ static struct FLAC__share__option long_options_[] = { { "verify", 0, 0, 'V' }, { "force-raw-format", 0, 0, 0 }, { "lax", 0, 0, 0 }, + { "replay-gain", 0, 0, 0 }, { "sector-align", 0, 0, 0 }, { "seekpoint", 1, 0, 'S' }, { "padding", 1, 0, 'P' }, @@ -159,6 +161,7 @@ static struct FLAC__share__option long_options_[] = { { "no-silent", 0, 0, 0 }, { "no-seektable", 0, 0, 0 }, { "no-delete-input-file", 0, 0, 0 }, + { "no-replay-gain", 0, 0, 0 }, { "no-sector-align", 0, 0, 0 }, { "no-lax", 0, 0, 0 }, #ifdef FLAC__HAS_OGG @@ -213,6 +216,7 @@ static struct { FLAC__bool force_to_stdout; FLAC__bool force_raw_format; FLAC__bool delete_input; + FLAC__bool replay_gain; FLAC__bool sector_align; const char *cmdline_forced_outfilename; const char *output_prefix; @@ -368,15 +372,27 @@ int do_it() if(option_values.sector_align) { if(option_values.mode_decode) return usage_error("ERROR: --sector-align only allowed for encoding\n"); - else if(option_values.skip > 0) + if(option_values.skip > 0) return usage_error("ERROR: --sector-align not allowed with --skip\n"); - else if(option_values.format_channels >= 0 && option_values.format_channels != 2) + if(option_values.format_channels >= 0 && option_values.format_channels != 2) return usage_error("ERROR: --sector-align can only be done with stereo input\n"); - else if(option_values.format_bps >= 0 && option_values.format_bps != 16) + if(option_values.format_bps >= 0 && option_values.format_bps != 16) return usage_error("ERROR: --sector-align can only be done with 16-bit samples\n"); - else if(option_values.format_sample_rate >= 0 && option_values.format_sample_rate != 44100) + if(option_values.format_sample_rate >= 0 && option_values.format_sample_rate != 44100) return usage_error("ERROR: --sector-align can only be done with a sample rate of 44100\n"); } + if(option_values.replay_gain) { + if(option_values.mode_decode) + return usage_error("ERROR: --replay-gain only allowed for encoding\n"); + if(option_values.skip > 0) + return usage_error("ERROR: --replay-gain not allowed with --skip\n"); + if(option_values.format_channels > 2) + return usage_error("ERROR: --replay-gain can only be done with mono/stereo input\n"); + if(option_values.format_sample_rate >= 0 && !FLAC__replaygain_is_valid_sample_frequency(option_values.format_sample_rate)) + return usage_error("ERROR: invalid sample rate used with --replay-gain\n"); + if(option_values.padding < 0) + fprintf(stderr, "WARNING: --replay-gain may leave a small PADDING block even with --no-padding\n"); + } if(option_values.num_files > 1 && option_values.cmdline_forced_outfilename) { return usage_error("ERROR: -o/--output-name cannot be used with multiple files\n"); } @@ -439,7 +455,7 @@ int do_it() FLAC__bool first = true; if(option_values.num_files == 0) { - retval = encode_file("-", 0, true); + retval = encode_file("-", 0, first, true); } else { unsigned i; @@ -448,7 +464,7 @@ int do_it() for(i = 0, retval = 0; i < option_values.num_files; i++) { if(0 == strcmp(option_values.filenames[i], "-") && !first) continue; - retval |= encode_file(option_values.filenames[i], 0, i == (option_values.num_files-1)); + retval |= encode_file(option_values.filenames[i], 0, first, i == (option_values.num_files-1)); first = false; } } @@ -479,6 +495,7 @@ FLAC__bool init_options() option_values.force_to_stdout = false; option_values.force_raw_format = false; option_values.delete_input = false; + option_values.replay_gain = false; option_values.sector_align = false; option_values.cmdline_forced_outfilename = 0; option_values.output_prefix = 0; @@ -589,6 +606,21 @@ int parse_option(int short_option, const char *long_option, const char *option_a else if(0 == strcmp(long_option, "lax")) { option_values.lax = true; } + else if(0 == strcmp(long_option, "replay-gain")) { + option_values.replay_gain = true; + /* + * We want to reserve space in the Vorbis comments + * for the ReplayGain tags that we will set later. + * Why do we store 100000.0 for the gain numbers + * initially? The gain field is variable length, so + * if the real number causes the tag size to shrink, + * we want it to shrink by at least 4 bytes so we can + * backfill with PADDING to avoid rewriting the whole + * file. + */ + if(0 != (violation = FLAC__replaygain_store_to_vorbiscomment(option_values.vorbis_comment, 100000.0, 0.0, 100000.0, 0.0))) + return usage_error("ERROR: (--replay-gain) %s\n", violation); + } else if(0 == strcmp(long_option, "sector-align")) { option_values.sector_align = true; } @@ -653,6 +685,9 @@ int parse_option(int short_option, const char *long_option, const char *option_a else if(0 == strcmp(long_option, "no-delete-input-file")) { option_values.delete_input = false; } + else if(0 == strcmp(long_option, "no-replay-gain")) { + option_values.replay_gain = false; + } else if(0 == strcmp(long_option, "no-sector-align")) { option_values.sector_align = false; } @@ -1022,6 +1057,7 @@ void show_help() #endif printf(" --lax Allow encoder to generate non-Subset files\n"); printf(" --sector-align Align multiple files on sector boundaries\n"); + printf(" --replay-gain Calculate ReplayGain & store in Vorbis comments\n"); printf(" -S, --seekpoint={#|X|#x} Add seek point(s)\n"); printf(" -P, --padding=# Write a PADDING block of length #\n"); printf(" -T, --tag=FIELD=VALUE Add a Vorbis comment; may appear multiple times\n"); @@ -1073,6 +1109,7 @@ void show_help() #endif printf(" --no-padding\n"); printf(" --no-qlp-coeff-prec-search\n"); + printf(" --no-replay-gain\n"); printf(" --no-residual-gnuplot\n"); printf(" --no-residual-text\n"); printf(" --no-sector-align\n"); @@ -1165,6 +1202,14 @@ void show_explain() printf(" --lax Allow encoder to generate non-Subset files\n"); printf(" --sector-align Align encoding of multiple CD format WAVE files\n"); printf(" on sector boundaries.\n"); + printf(" --replay-gain Calculate ReplayGain values and store in Vorbis\n"); + printf(" comments. Title gains/peaks will be computed\n"); + printf(" for each file, and an album gain/peak will be\n"); + printf(" computed for all files. All input files must\n"); + printf(" have the same resolution, sample rate, and\n"); + printf(" number of channels. The sample rate must be\n"); + printf(" one of 8, 11.025, 12, 16, 22.05, 24, 32, 44.1,\n"); + printf(" or 48 kHz.\n"); printf(" -S, --seekpoint={#|X|#x} Include a point or points in a SEEKTABLE\n"); printf(" # : a specific sample number for a seek point\n"); printf(" X : a placeholder point (always goes at the end of the SEEKTABLE)\n"); @@ -1277,7 +1322,7 @@ format_mistake(const char *infilename, const char *wrong, const char *right) fprintf(stderr, "WARNING: %s is not a %s file; treating as a %s file\n", infilename, wrong, right); } -int encode_file(const char *infilename, const char *forced_outfilename, FLAC__bool is_last_file) +int encode_file(const char *infilename, const char *forced_outfilename, FLAC__bool is_first_file, FLAC__bool is_last_file) { FILE *encode_infile; char outfilename[4096]; /* @@@ bad MAGIC NUMBER */ @@ -1384,9 +1429,11 @@ int encode_file(const char *infilename, const char *forced_outfilename, FLAC__bo common_options.padding = option_values.padding; common_options.requested_seek_points = option_values.requested_seek_points; common_options.num_requested_seek_points = option_values.num_requested_seek_points; + common_options.is_first_file = is_first_file; common_options.is_last_file = is_last_file; common_options.align_reservoir = align_reservoir; common_options.align_reservoir_samples = &align_reservoir_samples; + common_options.replay_gain = option_values.replay_gain; common_options.sector_align = option_values.sector_align; common_options.vorbis_comment = option_values.vorbis_comment; common_options.debug.disable_constant_subframes = option_values.debug.disable_constant_subframes;