add utils.c, utils.h
[flac.git] / src / flac / encode.c
index 150bd30..02da8eb 100644 (file)
 #include <stdlib.h> /* for malloc */
 #include <string.h> /* for strcmp() */
 #include "FLAC/all.h"
+#include "share/grabbag.h"
 #include "encode.h"
-#include "file.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
 #ifdef FLAC__HAS_OGG
 #include "OggFLAC/stream_encoder.h"
 #endif
@@ -53,6 +58,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;
@@ -95,6 +104,19 @@ static FLAC__int32 *input_[FLAC__MAX_CHANNELS];
 
 
 /*
+ * unpublished debug routines from the FLAC libs
+ */
+extern FLAC__bool FLAC__stream_encoder_disable_constant_subframes(FLAC__StreamEncoder *encoder, FLAC__bool value);
+extern FLAC__bool FLAC__stream_encoder_disable_fixed_subframes(FLAC__StreamEncoder *encoder, FLAC__bool value);
+extern FLAC__bool FLAC__stream_encoder_disable_verbatim_subframes(FLAC__StreamEncoder *encoder, FLAC__bool value);
+extern FLAC__bool FLAC__file_encoder_disable_constant_subframes(FLAC__FileEncoder *encoder, FLAC__bool value);
+extern FLAC__bool FLAC__file_encoder_disable_fixed_subframes(FLAC__FileEncoder *encoder, FLAC__bool value);
+extern FLAC__bool FLAC__file_encoder_disable_verbatim_subframes(FLAC__FileEncoder *encoder, FLAC__bool value);
+extern FLAC__bool OggFLAC__stream_encoder_disable_constant_subframes(OggFLAC__StreamEncoder *encoder, FLAC__bool value);
+extern FLAC__bool OggFLAC__stream_encoder_disable_fixed_subframes(OggFLAC__StreamEncoder *encoder, FLAC__bool value);
+extern FLAC__bool OggFLAC__stream_encoder_disable_verbatim_subframes(OggFLAC__StreamEncoder *encoder, FLAC__bool value);
+
+/*
  * local routines
  */
 static FLAC__bool EncoderSession_construct(EncoderSession *e, FLAC__bool use_ogg, FLAC__bool verify, FLAC__bool verbose, FILE *infile, const char *infilename, const char *outfilename);
@@ -103,7 +125,7 @@ static int EncoderSession_finish_ok(EncoderSession *e, int info_align_carry, int
 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);
 static FLAC__bool EncoderSession_process(EncoderSession *e, const FLAC__int32 * const buffer[], unsigned samples);
-static FLAC__bool convert_to_seek_table_template(char *requested_seek_points, int num_requested_seek_points, FLAC__uint64 stream_samples, FLAC__StreamMetadata *seek_table_template);
+static FLAC__bool convert_to_seek_table_template(const char *requested_seek_points, int num_requested_seek_points, FLAC__StreamMetadata *cuesheet, EncoderSession *e);
 static void format_input(FLAC__int32 *dest[], unsigned wide_samples, FLAC__bool is_big_endian, FLAC__bool is_unsigned_samples, unsigned channels, unsigned bps);
 #ifdef FLAC__HAS_OGG
 static FLAC__StreamEncoderWriteStatus ogg_stream_encoder_write_callback(const OggFLAC__StreamEncoder *encoder, const FLAC__byte buffer[], unsigned bytes, unsigned samples, unsigned current_frame, void *client_data);
@@ -111,6 +133,7 @@ static FLAC__StreamEncoderWriteStatus ogg_stream_encoder_write_callback(const Og
 static FLAC__StreamEncoderWriteStatus flac_stream_encoder_write_callback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], unsigned bytes, unsigned samples, unsigned current_frame, void *client_data);
 static void flac_stream_encoder_metadata_callback(const FLAC__StreamEncoder *encoder, const FLAC__StreamMetadata *metadata, void *client_data);
 static void flac_file_encoder_progress_callback(const FLAC__FileEncoder *encoder, FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate, void *client_data);
+static FLAC__bool parse_cuesheet_(FLAC__StreamMetadata **cuesheet, const char *cuesheet_filename, const char *inbasefilename, FLAC__uint64 lead_out_offset);
 static void print_stats(const EncoderSession *encoder_session);
 static void print_error_with_state(const EncoderSession *e, const char *message);
 static void print_verify_error(EncoderSession *e);
@@ -140,7 +163,21 @@ flac__encode_aif(FILE *infile, long infilesize, const char *infilename, const ch
        (void)lookahead; /* silence compiler warning about unused parameter */
        (void)lookahead_length; /* silence compiler warning about unused parameter */
 
-       if(!EncoderSession_construct(&encoder_session, options.common.use_ogg, options.common.verify, options.common.verbose, infile, infilename, outfilename))
+       if(!
+               EncoderSession_construct(
+                       &encoder_session,
+#ifdef FLAC__HAS_OGG
+                       options.common.use_ogg,
+#else
+                       /*use_ogg=*/false,
+#endif
+                       options.common.verify,
+                       options.common.verbose,
+                       infile,
+                       infilename,
+                       outfilename
+               )
+       )
                return 1;
 
        /* lookahead[] already has "FORMxxxxAIFF", do sub-chunks */
@@ -273,7 +310,7 @@ flac__encode_aif(FILE *infile, long infilesize, const char *infilename, const ch
                                /* is guaranteed to be less than LONG_MAX */
                                for(; remaining>0U; remaining-= remaining>(1U<<30) ? remaining : (1U<<30))
                                {
-                                       unsigned long skip= remaining % (1U<<30);
+                                       unsigned long skip= (unsigned long)(remaining % (1U<<30));
 
                                        FLAC__ASSERT(skip<=LONG_MAX);
                                        while(skip>0 && fseek(infile, skip, SEEK_CUR)<0) {
@@ -456,7 +493,21 @@ int flac__encode_wav(FILE *infile, long infilesize, const char *infilename, cons
        (void)lookahead;
        (void)lookahead_length;
 
-       if(!EncoderSession_construct(&encoder_session, options.common.use_ogg, options.common.verify, options.common.verbose, infile, infilename, outfilename))
+       if(!
+               EncoderSession_construct(
+                       &encoder_session,
+#ifdef FLAC__HAS_OGG
+                       options.common.use_ogg,
+#else
+                       /*use_ogg=*/false,
+#endif
+                       options.common.verify,
+                       options.common.verbose,
+                       infile,
+                       infilename,
+                       outfilename
+               )
+       )
                return 1;
 
        /*
@@ -737,8 +788,25 @@ 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 || grabbag__replaygain_is_valid_sample_frequency(options.sample_rate));
 
-       if(!EncoderSession_construct(&encoder_session, options.common.use_ogg, options.common.verify, options.common.verbose, infile, infilename, outfilename))
+       if(!
+               EncoderSession_construct(
+                       &encoder_session,
+#ifdef FLAC__HAS_OGG
+                       options.common.use_ogg,
+#else
+                       /*use_ogg=*/false,
+#endif
+                       options.common.verify,
+                       options.common.verbose,
+                       infile,
+                       infilename,
+                       outfilename
+               )
+       )
                return 1;
 
        /* get the file length */
@@ -918,13 +986,15 @@ FLAC__bool EncoderSession_construct(EncoderSession *e, FLAC__bool use_ogg, FLAC_
 
 #ifdef FLAC__HAS_OGG
        e->use_ogg = use_ogg;
+#else
+       (void)use_ogg;
 #endif
        e->verify = verify;
        e->verbose = verbose;
 
        e->is_stdout = (0 == strcmp(outfilename, "-"));
 
-       e->inbasefilename = flac__file_get_basename(infilename);
+       e->inbasefilename = grabbag__file_get_basename(infilename);
        e->outfilename = outfilename;
 
        e->unencoded_size = 0;
@@ -945,7 +1015,7 @@ FLAC__bool EncoderSession_construct(EncoderSession *e, FLAC__bool use_ogg, FLAC_
        e->seek_table_template = 0;
 
        if(e->is_stdout) {
-               e->fout = file__get_binary_stdout();
+               e->fout = grabbag__file_get_binary_stdout();
        }
 #ifdef FLAC__HAS_OGG
        else {
@@ -1109,14 +1179,41 @@ int EncoderSession_finish_error(EncoderSession *e)
 FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, unsigned channels, unsigned bps, unsigned sample_rate)
 {
        unsigned num_metadata;
-       FLAC__StreamMetadata padding;
-       FLAC__StreamMetadata *metadata[2];
+       FLAC__StreamMetadata padding, *cuesheet = 0;
+       FLAC__StreamMetadata *metadata[4];
+
+       e->replay_gain = options.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(!grabbag__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(options.is_first_file) {
+                       if(!grabbag__replaygain_init(sample_rate)) {
+                               fprintf(stderr, "%s: ERROR initializing ReplayGain stage\n", e->inbasefilename);
+                               return false;
+                       }
+               }
+       }
 
        if(channels != 2)
                options.do_mid_side = options.loose_mid_side = false;
 
-       if(!convert_to_seek_table_template(options.requested_seek_points, options.num_requested_seek_points, e->total_samples_to_encode, e->seek_table_template)) {
+       if(!parse_cuesheet_(&cuesheet, options.cuesheet_filename, e->inbasefilename, e->total_samples_to_encode))
+               return false;
+
+       if(!convert_to_seek_table_template(options.requested_seek_points, options.num_requested_seek_points, options.cued_seekpoints? cuesheet : 0, e)) {
                fprintf(stderr, "%s: ERROR allocating memory for seek table\n", e->inbasefilename);
+               if(0 != cuesheet)
+                       free(cuesheet);
                return false;
        }
 
@@ -1125,6 +1222,9 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio
                e->seek_table_template->is_last = false; /* the encoder will set this for us */
                metadata[num_metadata++] = e->seek_table_template;
        }
+       if(0 != cuesheet)
+               metadata[num_metadata++] = cuesheet;
+       metadata[num_metadata++] = options.vorbis_comment;
        if(options.padding > 0) {
                padding.is_last = false; /* the encoder will set this for us */
                padding.type = FLAC__METADATA_TYPE_PADDING;
@@ -1137,6 +1237,8 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio
 
 #ifdef FLAC__HAS_OGG
        if(e->use_ogg) {
+               if(options.has_serial_number)
+                       OggFLAC__stream_encoder_set_serial_number(e->encoder.ogg.stream, options.serial_number);
                OggFLAC__stream_encoder_set_verify(e->encoder.ogg.stream, options.verify);
                OggFLAC__stream_encoder_set_streamable_subset(e->encoder.ogg.stream, !options.lax);
                OggFLAC__stream_encoder_set_do_mid_side_stereo(e->encoder.ogg.stream, options.do_mid_side);
@@ -1158,8 +1260,14 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio
                OggFLAC__stream_encoder_set_write_callback(e->encoder.ogg.stream, ogg_stream_encoder_write_callback);
                OggFLAC__stream_encoder_set_client_data(e->encoder.ogg.stream, e);
 
+               OggFLAC__stream_encoder_disable_constant_subframes(e->encoder.ogg.stream, options.debug.disable_constant_subframes);
+               OggFLAC__stream_encoder_disable_fixed_subframes(e->encoder.ogg.stream, options.debug.disable_fixed_subframes);
+               OggFLAC__stream_encoder_disable_verbatim_subframes(e->encoder.ogg.stream, options.debug.disable_verbatim_subframes);
+
                if(OggFLAC__stream_encoder_init(e->encoder.ogg.stream) != FLAC__STREAM_ENCODER_OK) {
                        print_error_with_state(e, "ERROR initializing encoder");
+                       if(0 != cuesheet)
+                               free(cuesheet);
                        return false;
                }
        }
@@ -1188,8 +1296,14 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio
                FLAC__stream_encoder_set_metadata_callback(e->encoder.flac.stream, flac_stream_encoder_metadata_callback);
                FLAC__stream_encoder_set_client_data(e->encoder.flac.stream, e);
 
+               FLAC__stream_encoder_disable_constant_subframes(e->encoder.flac.stream, options.debug.disable_constant_subframes);
+               FLAC__stream_encoder_disable_fixed_subframes(e->encoder.flac.stream, options.debug.disable_fixed_subframes);
+               FLAC__stream_encoder_disable_verbatim_subframes(e->encoder.flac.stream, options.debug.disable_verbatim_subframes);
+
                if(FLAC__stream_encoder_init(e->encoder.flac.stream) != FLAC__STREAM_ENCODER_OK) {
                        print_error_with_state(e, "ERROR initializing encoder");
+                       if(0 != cuesheet)
+                               free(cuesheet);
                        return false;
                }
        }
@@ -1216,17 +1330,31 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio
                FLAC__file_encoder_set_progress_callback(e->encoder.flac.file, flac_file_encoder_progress_callback);
                FLAC__file_encoder_set_client_data(e->encoder.flac.file, e);
 
+               FLAC__file_encoder_disable_constant_subframes(e->encoder.flac.file, options.debug.disable_constant_subframes);
+               FLAC__file_encoder_disable_fixed_subframes(e->encoder.flac.file, options.debug.disable_fixed_subframes);
+               FLAC__file_encoder_disable_verbatim_subframes(e->encoder.flac.file, options.debug.disable_verbatim_subframes);
+
                if(FLAC__file_encoder_init(e->encoder.flac.file) != FLAC__FILE_ENCODER_OK) {
                        print_error_with_state(e, "ERROR initializing encoder");
+                       if(0 != cuesheet)
+                               free(cuesheet);
                        return false;
                }
        }
 
+       if(0 != cuesheet)
+               free(cuesheet);
+
        return true;
 }
 
 FLAC__bool EncoderSession_process(EncoderSession *e, const FLAC__int32 * const buffer[], unsigned samples)
 {
+       if(e->replay_gain) {
+               if(!grabbag__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);
@@ -1241,68 +1369,57 @@ FLAC__bool EncoderSession_process(EncoderSession *e, const FLAC__int32 * const b
        }
 }
 
-FLAC__bool convert_to_seek_table_template(char *requested_seek_points, int num_requested_seek_points, FLAC__uint64 stream_samples, FLAC__StreamMetadata *seek_table_template)
+FLAC__bool convert_to_seek_table_template(const char *requested_seek_points, int num_requested_seek_points, FLAC__StreamMetadata *cuesheet, EncoderSession *e)
 {
-       unsigned i, real_points, placeholders;
-       char *pt = requested_seek_points, *q;
+       FLAC__bool only_placeholders;
+       FLAC__bool has_real_points;
 
-       if(num_requested_seek_points == 0)
+       if(num_requested_seek_points == 0 && 0 == cuesheet)
                return true;
 
        if(num_requested_seek_points < 0) {
-               strcpy(requested_seek_points, "100x<");
+               requested_seek_points = "100x;";
                num_requested_seek_points = 1;
        }
 
-       /* first count how many individual seek points we may need */
-       real_points = placeholders = 0;
-       for(i = 0; i < (unsigned)num_requested_seek_points; i++) {
-               q = strchr(pt, '<');
-               FLAC__ASSERT(0 != q);
-               *q = '\0';
-
-               if(0 == strcmp(pt, "X")) { /* -S X */
-                       placeholders++;
-               }
-               else if(pt[strlen(pt)-1] == 'x') { /* -S #x */
-                       if(stream_samples > 0) /* we can only do these if we know the number of samples to encode up front */
-                               real_points += (unsigned)atoi(pt);
-               }
-               else { /* -S # */
-                       real_points++;
-               }
-               *q++ = '<';
+       if(e->is_stdout)
+               only_placeholders = true;
+#ifdef FLAC__HAS_OGG
+       else if(e->use_ogg)
+               only_placeholders = true;
+#endif
+       else
+               only_placeholders = false;
 
-               pt = q;
+       if(num_requested_seek_points > 0) {
+               if(!grabbag__seektable_convert_specification_to_template(requested_seek_points, only_placeholders, e->total_samples_to_encode, e->sample_rate, e->seek_table_template, &has_real_points))
+                       return false;
        }
-       pt = requested_seek_points;
-
-       for(i = 0; i < (unsigned)num_requested_seek_points; i++) {
-               q = strchr(pt, '<');
-               FLAC__ASSERT(0 != q);
-               *q++ = '\0';
 
-               if(0 == strcmp(pt, "X")) { /* -S X */
-                       if(!FLAC__metadata_object_seektable_template_append_placeholders(seek_table_template, 1))
-                               return false;
-               }
-               else if(pt[strlen(pt)-1] == 'x') { /* -S #x */
-                       if(stream_samples > 0) { /* we can only do these if we know the number of samples to encode up front */
-                               if(!FLAC__metadata_object_seektable_template_append_spaced_points(seek_table_template, atoi(pt), stream_samples))
+       if(0 != cuesheet) {
+               unsigned i, j;
+               const FLAC__StreamMetadata_CueSheet *cs = &cuesheet->data.cue_sheet;
+               for(i = 0; i < cs->num_tracks; i++) {
+                       const FLAC__StreamMetadata_CueSheet_Track *tr = cs->tracks+i;
+                       for(j = 0; j < tr->num_indices; j++) {
+                               if(!FLAC__metadata_object_seektable_template_append_point(e->seek_table_template, tr->offset + tr->indices[j].offset))
                                        return false;
+                               has_real_points = true;
                        }
                }
-               else { /* -S # */
-                       FLAC__uint64 n = (unsigned)atoi(pt);
-                       if(!FLAC__metadata_object_seektable_template_append_point(seek_table_template, n))
+               if(has_real_points)
+                       if(!FLAC__metadata_object_seektable_template_sort(e->seek_table_template, /*compact=*/true))
                                return false;
-               }
-
-               pt = q;
        }
 
-       if(!FLAC__metadata_object_seektable_template_sort(seek_table_template, /*compact=*/true))
-               return false;
+       if(has_real_points) {
+               if(e->is_stdout)
+                       fprintf(stderr, "%s: WARNING, cannot write back seekpoints when encoding to stdout\n", e->inbasefilename);
+#ifdef FLAC__HAS_OGG
+               else if(e->use_ogg)
+                       fprintf(stderr, "%s: WARNING, cannot write back seekpoints when encoding to Ogg\n", e->inbasefilename);
+#endif
+       }
 
        return true;
 }
@@ -1382,6 +1499,8 @@ FLAC__StreamEncoderWriteStatus ogg_stream_encoder_write_callback(const OggFLAC__
 {
        EncoderSession *encoder_session = (EncoderSession*)client_data;
 
+       (void)encoder;
+
        encoder_session->bytes_written += bytes;
        /*
         * With Ogg FLAC we don't get one write callback per frame and
@@ -1442,6 +1561,37 @@ void flac_file_encoder_progress_callback(const FLAC__FileEncoder *encoder, FLAC_
                print_stats(encoder_session);
 }
 
+FLAC__bool parse_cuesheet_(FLAC__StreamMetadata **cuesheet, const char *cuesheet_filename, const char *inbasefilename, FLAC__uint64 lead_out_offset)
+{
+       FILE *f;
+       unsigned last_line_read;
+       const char *error_message;
+
+       if(0 == cuesheet_filename)
+               return true;
+
+       if(lead_out_offset == 0) {
+               fprintf(stderr, "%s: ERROR cannot import cuesheet when the number of input samples to encode is unknown\n", inbasefilename);
+               return false;
+       }
+
+       if(0 == (f = fopen(cuesheet_filename, "r"))) {
+               fprintf(stderr, "%s: ERROR opening cuesheet \"%s\" for reading\n", inbasefilename, cuesheet_filename);
+               return false;
+       }
+
+       *cuesheet = grabbag__cuesheet_parse(f, &error_message, &last_line_read, /*@@@@is_cdda=*/true, lead_out_offset);
+
+       fclose(f);
+
+       if(0 == *cuesheet) {
+               fprintf(stderr, "%s: ERROR parsing cuesheet \"%s\" on line %u: %s\n", inbasefilename, cuesheet_filename, last_line_read, error_message);
+               return false;
+       }
+
+       return true;
+}
+
 void print_stats(const EncoderSession *encoder_session)
 {
        const FLAC__uint64 samples_written = min(encoder_session->total_samples_to_encode, encoder_session->samples_written);
@@ -1676,7 +1826,7 @@ read_sane_extended(FILE *f, FLAC__uint32 *val, FLAC__bool eof_ok, const char *fn
 
        for(i= 0U; i<8U; ++i)
                p|= (FLAC__uint64)(buf[i+2])<<(56U-i*8);
-       *val= (FLAC__uint32)(p>>shift)+(p>>(shift-1) & 0x1);
+       *val= (FLAC__uint32)((p>>shift)+(p>>(shift-1) & 0x1));
 
        return true;
 }