diff options
Diffstat (limited to 'src/flac/encode.c')
-rw-r--r-- | src/flac/encode.c | 534 |
1 files changed, 508 insertions, 26 deletions
diff --git a/src/flac/encode.c b/src/flac/encode.c index 6528c7a4..8bafa5bd 100644 --- a/src/flac/encode.c +++ b/src/flac/encode.c @@ -103,6 +103,19 @@ typedef struct { FLAC__StreamMetadata *seek_table_template; } EncoderSession; +/* this is data attached to the FLAC decoder when encoding from a FLAC file */ +typedef struct { + EncoderSession *encoder_session; + off_t filesize; + const FLAC__byte *lookahead; + unsigned lookahead_length; + size_t num_metadata_blocks; + FLAC__StreamMetadata *metadata_blocks[1024]; /*@@@ BAD MAGIC number */ + FLAC__uint64 samples_left_to_process; + FLAC__bool fatal_error; +} FLACDecoderData; + +const int FLAC_ENCODE__DEFAULT_PADDING = 4096; static FLAC__bool is_big_endian_host_; @@ -140,7 +153,7 @@ 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); +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_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); @@ -153,7 +166,15 @@ static void ogg_file_encoder_progress_callback(const OggFLAC__FileEncoder *encod 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__bool is_cdda, FLAC__uint64 lead_out_offset); +static FLAC__SeekableStreamDecoderReadStatus flac_decoder_read_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], unsigned *bytes, void *client_data); +static FLAC__SeekableStreamDecoderSeekStatus flac_decoder_seek_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data); +static FLAC__SeekableStreamDecoderTellStatus flac_decoder_tell_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data); +static FLAC__SeekableStreamDecoderLengthStatus flac_decoder_length_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data); +static FLAC__bool flac_decoder_eof_callback(const FLAC__SeekableStreamDecoder *decoder, void *client_data); +static FLAC__StreamDecoderWriteStatus flac_decoder_write_callback(const FLAC__SeekableStreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data); +static void flac_decoder_metadata_callback(const FLAC__SeekableStreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data); +static void flac_decoder_error_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data); +static FLAC__bool parse_cuesheet(FLAC__StreamMetadata **cuesheet, const char *cuesheet_filename, const char *inbasefilename, FLAC__bool is_cdda, 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); @@ -386,7 +407,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)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, channels, bps, sample_rate, /*flac_decoder_data=*/0)) return EncoderSession_finish_error(&encoder_session); /* first do any samples in the reservoir */ @@ -714,7 +735,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, sample_rate)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, channels, bps, sample_rate, /*flac_decoder_data=*/0)) return EncoderSession_finish_error(&encoder_session); /* @@ -963,7 +984,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)) + if(!EncoderSession_init_encoder(&encoder_session, options.common, options.channels, options.bps, options.sample_rate, /*flac_decoder_data=*/0)) return EncoderSession_finish_error(&encoder_session); /* @@ -1036,7 +1057,7 @@ int flac__encode_raw(FILE *infile, off_t infilesize, const char *infilename, con while(total_input_bytes_read < max_input_bytes) { { size_t wanted = (CHUNK_OF_SAMPLES * bytes_per_wide_sample); - wanted = (size_t) min((off_t)wanted, max_input_bytes - total_input_bytes_read); + wanted = (size_t) min((FLAC__uint64)wanted, max_input_bytes - total_input_bytes_read); if(lookahead_length > 0) { FLAC__ASSERT(lookahead_length <= wanted); @@ -1126,6 +1147,163 @@ int flac__encode_raw(FILE *infile, off_t infilesize, const char *infilename, con return EncoderSession_finish_ok(&encoder_session, info_align_carry, info_align_zero); } +int flac__encode_flac(FILE *infile, off_t infilesize, const char *infilename, const char *outfilename, const FLAC__byte *lookahead, unsigned lookahead_length, flac_encode_options_t options) +{ + EncoderSession encoder_session; + FLAC__SeekableStreamDecoder *decoder = 0; + FLACDecoderData decoder_data; + size_t i; + int retval; + + if(! + EncoderSession_construct( + &encoder_session, +#ifdef FLAC__HAS_OGG + options.common.use_ogg, +#else + /*use_ogg=*/false, +#endif + options.common.verify, + infile, + infilename, + outfilename + ) + ) + return 1; + + decoder_data.encoder_session = &encoder_session; + decoder_data.filesize = (infilesize == (off_t)(-1)? 0 : infilesize); + decoder_data.lookahead = lookahead; + decoder_data.lookahead_length = lookahead_length; + decoder_data.num_metadata_blocks = 0; + decoder_data.samples_left_to_process = 0; + decoder_data.fatal_error = false; + + /* + * set up FLAC decoder for the input + */ + if (0 == (decoder = FLAC__seekable_stream_decoder_new())) { + flac__utils_printf(stderr, 1, "%s: ERROR: creating decoder for FLAC input\n", encoder_session.inbasefilename); + return EncoderSession_finish_error(&encoder_session); + } + if (!( + FLAC__seekable_stream_decoder_set_md5_checking(decoder, false) && + FLAC__seekable_stream_decoder_set_read_callback(decoder, flac_decoder_read_callback) && + FLAC__seekable_stream_decoder_set_seek_callback(decoder, flac_decoder_seek_callback) && + FLAC__seekable_stream_decoder_set_tell_callback(decoder, flac_decoder_tell_callback) && + FLAC__seekable_stream_decoder_set_length_callback(decoder, flac_decoder_length_callback) && + FLAC__seekable_stream_decoder_set_eof_callback(decoder, flac_decoder_eof_callback) && + FLAC__seekable_stream_decoder_set_write_callback(decoder, flac_decoder_write_callback) && + FLAC__seekable_stream_decoder_set_metadata_callback(decoder, flac_decoder_metadata_callback) && + FLAC__seekable_stream_decoder_set_error_callback(decoder, flac_decoder_error_callback) && + FLAC__seekable_stream_decoder_set_client_data(decoder, &decoder_data) && + FLAC__seekable_stream_decoder_set_metadata_respond_all(decoder) + )) { + flac__utils_printf(stderr, 1, "%s: ERROR: setting up decoder for FLAC input\n", encoder_session.inbasefilename); + goto fubar1; /*@@@ yuck */ + } + + if (FLAC__seekable_stream_decoder_init(decoder) != FLAC__SEEKABLE_STREAM_DECODER_OK) { + flac__utils_printf(stderr, 1, "%s: ERROR: initializing decoder for FLAC input, state = %s\n", encoder_session.inbasefilename, FLAC__seekable_stream_decoder_get_resolved_state_string(decoder)); + goto fubar1; /*@@@ yuck */ + } + + if (!FLAC__seekable_stream_decoder_process_until_end_of_metadata(decoder) || decoder_data.fatal_error) { + if (decoder_data.fatal_error) + flac__utils_printf(stderr, 1, "%s: ERROR: out of memory or too many metadata blocks while reading metadata in FLAC input\n", encoder_session.inbasefilename); + else + flac__utils_printf(stderr, 1, "%s: ERROR: reading metadata in FLAC input, state = %s\n", encoder_session.inbasefilename, FLAC__seekable_stream_decoder_get_resolved_state_string(decoder)); + goto fubar1; /*@@@ yuck */ + } + + if (decoder_data.num_metadata_blocks == 0) { + flac__utils_printf(stderr, 1, "%s: ERROR: reading metadata in FLAC input, got no metadata blocks\n", encoder_session.inbasefilename); + goto fubar2; /*@@@ yuck */ + } + else if (decoder_data.metadata_blocks[0]->type != FLAC__METADATA_TYPE_STREAMINFO) { + flac__utils_printf(stderr, 1, "%s: ERROR: reading metadata in FLAC input, first metadata block is not STREAMINFO\n", encoder_session.inbasefilename); + goto fubar2; /*@@@ yuck */ + } + else if (decoder_data.metadata_blocks[0]->data.stream_info.total_samples == 0) { + flac__utils_printf(stderr, 1, "%s: ERROR: FLAC input has STREAMINFO with unknown total samples which is not supported\n", encoder_session.inbasefilename); + goto fubar2; /*@@@ yuck */ + } + + /* + * now that we have the STREAMINFO and know the sample rate, + * canonicalize the --skip string to a number of samples: + */ + flac__utils_canonicalize_skip_until_specification(&options.common.skip_specification, decoder_data.metadata_blocks[0]->data.stream_info.sample_rate); + FLAC__ASSERT(options.common.skip_specification.value.samples >= 0); + encoder_session.skip = (FLAC__uint64)options.common.skip_specification.value.samples; + FLAC__ASSERT(!options.common.sector_align); /* --sector-align with FLAC input is not supported */ + + { + FLAC__uint64 total_samples_in_input, trim = 0; + + total_samples_in_input = decoder_data.metadata_blocks[0]->data.stream_info.total_samples; + + /* + * now that we know the input size, canonicalize the + * --until string to an absolute sample number: + */ + if(!canonicalize_until_specification(&options.common.until_specification, encoder_session.inbasefilename, decoder_data.metadata_blocks[0]->data.stream_info.sample_rate, encoder_session.skip, total_samples_in_input)) + goto fubar2; /*@@@ yuck */ + encoder_session.until = (FLAC__uint64)options.common.until_specification.value.samples; + + encoder_session.total_samples_to_encode = total_samples_in_input - encoder_session.skip; + if(encoder_session.until > 0) { + trim = total_samples_in_input - encoder_session.until; + FLAC__ASSERT(total_samples_in_input > 0); + encoder_session.total_samples_to_encode -= trim; + } + + 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)) + return EncoderSession_finish_error(&encoder_session); + + /* + * have to wait until the FLAC encoder is set up for writing + * before any seeking in the input FLAC file, because the seek + * itself will usually call the decoder's write callback, and + * our decoder's write callback passes samples to our FLAC + * encoder + */ + decoder_data.samples_left_to_process = encoder_session.total_samples_to_encode; + if(encoder_session.skip > 0) { + if(!FLAC__seekable_stream_decoder_seek_absolute(decoder, encoder_session.skip)) { + flac__utils_printf(stderr, 1, "%s: ERROR while skipping samples, FLAC decoder state = %s\n", encoder_session.inbasefilename, FLAC__seekable_stream_decoder_get_resolved_state_string(decoder)); + goto fubar2; /*@@@ yuck */ + } + } + + /* + * now do samples from the file + */ + while(!decoder_data.fatal_error && decoder_data.samples_left_to_process > 0) { + if(!FLAC__seekable_stream_decoder_process_single(decoder)) { + flac__utils_printf(stderr, 1, "%s: ERROR: while decoding FLAC input, state = %s\n", encoder_session.inbasefilename, FLAC__seekable_stream_decoder_get_resolved_state_string(decoder)); + goto fubar2; /*@@@ yuck */ + } + } + } + + FLAC__seekable_stream_decoder_delete(decoder); + retval = EncoderSession_finish_ok(&encoder_session, -1, -1); + /* have to wail until encoder is completely finished before deleting because of the final step of writing the seekpoint offsets */ + for(i = 0; i < decoder_data.num_metadata_blocks; i++) + free(decoder_data.metadata_blocks[i]); + return retval; + +fubar2: + for(i = 0; i < decoder_data.num_metadata_blocks; i++) + free(decoder_data.metadata_blocks[i]); +fubar1: + FLAC__seekable_stream_decoder_delete(decoder); + return EncoderSession_finish_error(&encoder_session); +} + FLAC__bool EncoderSession_construct(EncoderSession *e, FLAC__bool use_ogg, FLAC__bool verify, FILE *infile, const char *infilename, const char *outfilename) { unsigned i; @@ -1355,6 +1533,7 @@ int EncoderSession_finish_error(EncoderSession *e) if(fse_state == FLAC__STREAM_ENCODER_VERIFY_MISMATCH_IN_AUDIO_DATA) print_verify_error(e); else + /*@@@@@@@@@ BUG: if error was caused because the output file already exists but the file encoder could not write on top of it (i.e. it's not writable), this will delete the pre-existing file, which is not what we want */ unlink(e->outfilename); EncoderSession_destroy(e); @@ -1362,11 +1541,12 @@ 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) +FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t options, unsigned channels, unsigned bps, unsigned sample_rate, FLACDecoderData *flac_decoder_data) { unsigned num_metadata; FLAC__StreamMetadata padding, *cuesheet = 0; - FLAC__StreamMetadata *metadata[4]; + FLAC__StreamMetadata *static_metadata[4]; + FLAC__StreamMetadata **metadata = static_metadata; const FLAC__bool is_cdda = (channels == 1 || channels == 2) && (bps == 16) && (sample_rate == 44100); e->replay_gain = options.replay_gain; @@ -1394,7 +1574,7 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio if(channels != 2) options.do_mid_side = options.loose_mid_side = false; - if(!parse_cuesheet_(&cuesheet, options.cuesheet_filename, e->inbasefilename, is_cdda, e->total_samples_to_encode)) + if(!parse_cuesheet(&cuesheet, options.cuesheet_filename, e->inbasefilename, is_cdda, 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)) { @@ -1404,19 +1584,206 @@ FLAC__bool EncoderSession_init_encoder(EncoderSession *e, encode_options_t optio return false; } - num_metadata = 0; - if(e->seek_table_template->data.seek_table.num_points > 0) { - e->seek_table_template->is_last = false; /* the encoder will set this for us */ - metadata[num_metadata++] = e->seek_table_template; + if(flac_decoder_data) { + /* + * we're encoding from FLAC so we will use the FLAC file's + * metadata as the basic for the encoded file + */ + { + /* + * first handle padding: if --no-padding was specified, + * then delete all padding; else if -P was specified, + * use that instead of existing padding (if any); else + * if existing file has padding, move all existing + * padding blocks to one padding block at the end; else + * use default padding. + */ + int p = -1; + size_t i, j; + for(i = 0, j = 0; i < flac_decoder_data->num_metadata_blocks; i++) { + if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_PADDING) { + if(p < 0) + p = 0; + p += flac_decoder_data->metadata_blocks[i]->length; + FLAC__metadata_object_delete(flac_decoder_data->metadata_blocks[i]); + flac_decoder_data->metadata_blocks[i] = 0; + } + else + flac_decoder_data->metadata_blocks[j++] = flac_decoder_data->metadata_blocks[i]; + } + flac_decoder_data->num_metadata_blocks = j; + if(options.padding > 0) + p = options.padding; + if(p < 0) + p = FLAC_ENCODE__DEFAULT_PADDING; + if(options.padding != 0) { + if(p > 0 && flac_decoder_data->num_metadata_blocks < sizeof(flac_decoder_data->metadata_blocks)/sizeof(flac_decoder_data->metadata_blocks[0])) { + flac_decoder_data->metadata_blocks[flac_decoder_data->num_metadata_blocks] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); + if(0 == flac_decoder_data->metadata_blocks[flac_decoder_data->num_metadata_blocks]) { + flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for PADDING block\n", e->inbasefilename); + if(0 != cuesheet) + FLAC__metadata_object_delete(cuesheet); + return false; + } + flac_decoder_data->metadata_blocks[flac_decoder_data->num_metadata_blocks]->is_last = false; /* the encoder will set this for us */ + flac_decoder_data->metadata_blocks[flac_decoder_data->num_metadata_blocks]->length = p; + flac_decoder_data->num_metadata_blocks++; + } + } + } + { + /* + * 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 + */ + size_t i, j; + FLAC__bool vc_found = false; + for(i = 0, j = 0; i < flac_decoder_data->num_metadata_blocks; i++) { + 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); + FLAC__metadata_object_delete(flac_decoder_data->metadata_blocks[i]); + flac_decoder_data->metadata_blocks[i] = 0; + } + else + flac_decoder_data->metadata_blocks[j++] = flac_decoder_data->metadata_blocks[i]; + } + flac_decoder_data->num_metadata_blocks = j; + 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) { + flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for VORBIS_COMMENT block\n", e->inbasefilename); + if(0 != cuesheet) + FLAC__metadata_object_delete(cuesheet); + return false; + } + for(i = flac_decoder_data->num_metadata_blocks; i > 1; i--) + flac_decoder_data->metadata_blocks[i] = flac_decoder_data->metadata_blocks[i-1]; + flac_decoder_data->metadata_blocks[1] = vc; + flac_decoder_data->num_metadata_blocks++; + } + } + { + /* + * next handle cuesheet: if --cuesheet was specified, use + * it; else if file has existing CUESHEET and cuesheet's + * lead-out offset is correct, keep it; else no CUESHEET + */ + size_t i, j; + for(i = 0, j = 0; i < flac_decoder_data->num_metadata_blocks; i++) { + FLAC__bool existing_cuesheet_is_bad = false; + /* check if existing cuesheet matches the input audio */ + if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_CUESHEET && 0 == cuesheet) { + const FLAC__StreamMetadata_CueSheet *cs = &flac_decoder_data->metadata_blocks[i]->data.cue_sheet; + if(e->total_samples_to_encode == 0) { + flac__utils_printf(stderr, 1, "%s: WARNING, cuesheet in input FLAC file cannot be kept if input size is not known, dropping it...\n", e->inbasefilename); + existing_cuesheet_is_bad = true; + } + else if(e->total_samples_to_encode != cs->tracks[cs->num_tracks-1].offset) { + flac__utils_printf(stderr, 1, "%s: WARNING, lead-out offset of cuesheet in input FLAC file does not match input length, dropping existing cuesheet...\n", e->inbasefilename); + existing_cuesheet_is_bad = true; + } + } + if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_CUESHEET && (existing_cuesheet_is_bad || 0 != cuesheet)) { + if(0 != cuesheet) + flac__utils_printf(stderr, 1, "%s: WARNING, replacing cuesheet in input FLAC file with the one 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; + } + else + flac_decoder_data->metadata_blocks[j++] = flac_decoder_data->metadata_blocks[i]; + } + flac_decoder_data->num_metadata_blocks = j; + if(0 != cuesheet && flac_decoder_data->num_metadata_blocks < sizeof(flac_decoder_data->metadata_blocks)/sizeof(flac_decoder_data->metadata_blocks[0])) { + /* prepend ours */ + FLAC__StreamMetadata *cs = FLAC__metadata_object_clone(cuesheet); + if(0 == cs) { + flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for CUESHEET block\n", e->inbasefilename); + if(0 != cuesheet) + FLAC__metadata_object_delete(cuesheet); + return false; + } + for(i = flac_decoder_data->num_metadata_blocks; i > 1; i--) + flac_decoder_data->metadata_blocks[i] = flac_decoder_data->metadata_blocks[i-1]; + flac_decoder_data->metadata_blocks[1] = cs; + flac_decoder_data->num_metadata_blocks++; + } + } + { + /* + * finally handle seektable: if -S- was specified, no + * SEEKTABLE; else if -S was specified, use it/them; + * else if file has existing SEEKTABLE and input size is + * preserved (no --skip/--until/etc specified), keep it; + * else use default seektable options + * + * note: meanings of num_requested_seek_points: + * -1 : no -S option given, default to some value + * 0 : -S- given (no seektable) + * >0 : one or more -S options given + */ + size_t i, j; + FLAC__bool existing_seektable = false; + for(i = 0, j = 0; i < flac_decoder_data->num_metadata_blocks; i++) { + if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_SEEKTABLE) + existing_seektable = true; + if(flac_decoder_data->metadata_blocks[i]->type == FLAC__METADATA_TYPE_SEEKTABLE && (e->total_samples_to_encode != flac_decoder_data->metadata_blocks[0]->data.stream_info.total_samples || options.num_requested_seek_points >= 0)) { + if(options.num_requested_seek_points > 0) + flac__utils_printf(stderr, 1, "%s: WARNING, replacing seektable in input FLAC file with the one given on the command-line\n", e->inbasefilename); + else if(options.num_requested_seek_points == 0) + ; /* no warning, silently delete existing SEEKTABLE since user specified --no-seektable (-S-) */ + else + flac__utils_printf(stderr, 1, "%s: WARNING, can't use existing seektable in input FLAC since the input size is changing or unknown, dropping existing SEEKTABLE block...\n", e->inbasefilename); + FLAC__metadata_object_delete(flac_decoder_data->metadata_blocks[i]); + flac_decoder_data->metadata_blocks[i] = 0; + existing_seektable = false; + } + else + flac_decoder_data->metadata_blocks[j++] = flac_decoder_data->metadata_blocks[i]; + } + flac_decoder_data->num_metadata_blocks = j; + if((options.num_requested_seek_points > 0 || (options.num_requested_seek_points < 0 && !existing_seektable)) && flac_decoder_data->num_metadata_blocks < sizeof(flac_decoder_data->metadata_blocks)/sizeof(flac_decoder_data->metadata_blocks[0])) { + /* prepend ours */ + FLAC__StreamMetadata *st = FLAC__metadata_object_clone(e->seek_table_template); + if(0 == st) { + flac__utils_printf(stderr, 1, "%s: ERROR allocating memory for SEEKTABLE block\n", e->inbasefilename); + if(0 != cuesheet) + FLAC__metadata_object_delete(cuesheet); + return false; + } + for(i = flac_decoder_data->num_metadata_blocks; i > 1; i--) + flac_decoder_data->metadata_blocks[i] = flac_decoder_data->metadata_blocks[i-1]; + flac_decoder_data->metadata_blocks[1] = st; + flac_decoder_data->num_metadata_blocks++; + } + } + metadata = &flac_decoder_data->metadata_blocks[1]; /* don't include STREAMINFO */ + num_metadata = flac_decoder_data->num_metadata_blocks - 1; } - 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; - padding.length = (unsigned)options.padding; - metadata[num_metadata++] = &padding; + else { + /* + * we're not encoding from FLAC so we will build the metadata + * from scratch + */ + num_metadata = 0; + if(e->seek_table_template->data.seek_table.num_points > 0) { + e->seek_table_template->is_last = false; /* the encoder will set this for us */ + 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; + padding.length = (unsigned)(options.padding>0? options.padding : FLAC_ENCODE__DEFAULT_PADDING); + metadata[num_metadata++] = &padding; + } } e->blocksize = options.blocksize; @@ -1850,7 +2217,122 @@ 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__bool is_cdda, FLAC__uint64 lead_out_offset) +FLAC__SeekableStreamDecoderReadStatus flac_decoder_read_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], unsigned *bytes, void *client_data) +{ + size_t n = 0; + FLACDecoderData *data = (FLACDecoderData*)client_data; + (void)decoder; + + if (data->fatal_error) + return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; + + /* use up lookahead first */ + if (data->lookahead_length) { + n = min(data->lookahead_length, *bytes); + memcpy(buffer, data->lookahead, n); + buffer += n; + data->lookahead += n; + data->lookahead_length -= n; + } + + /* get the rest from file */ + if (*bytes > n) { + *bytes = n + fread(buffer, 1, *bytes-n, data->encoder_session->fin); + return ferror(data->encoder_session->fin)? FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR : FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; + } + else + return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; +} + +FLAC__SeekableStreamDecoderSeekStatus flac_decoder_seek_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) +{ + FLACDecoderData *data = (FLACDecoderData*)client_data; + (void)decoder; + + if(fseeko(data->encoder_session->fin, (off_t)absolute_byte_offset, SEEK_SET) < 0) + return FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; + else + return FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK; +} + +FLAC__SeekableStreamDecoderTellStatus flac_decoder_tell_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + FLACDecoderData *data = (FLACDecoderData*)client_data; + off_t pos; + (void)decoder; + + if((pos = ftello(data->encoder_session->fin)) < 0) + return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR; + else { + *absolute_byte_offset = (FLAC__uint64)pos; + return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK; + } +} + +FLAC__SeekableStreamDecoderLengthStatus flac_decoder_length_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) +{ + FLACDecoderData *data = (FLACDecoderData*)client_data; + (void)decoder; + + if(0 == data->filesize) + return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR; + else { + *stream_length = (FLAC__uint64)data->filesize; + return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK; + } +} + +FLAC__bool flac_decoder_eof_callback(const FLAC__SeekableStreamDecoder *decoder, void *client_data) +{ + FLACDecoderData *data = (FLACDecoderData*)client_data; + (void)decoder; + + return feof(data->encoder_session->fin)? true : false; +} + +FLAC__StreamDecoderWriteStatus flac_decoder_write_callback(const FLAC__SeekableStreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) +{ + FLACDecoderData *data = (FLACDecoderData*)client_data; + FLAC__uint64 n = min(data->samples_left_to_process, frame->header.blocksize); + (void)decoder; + + if(!EncoderSession_process(data->encoder_session, buffer, n)) { + print_error_with_state(data->encoder_session, "ERROR during encoding"); + data->fatal_error = true; + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + + data->samples_left_to_process -= n; + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +void flac_decoder_metadata_callback(const FLAC__SeekableStreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + FLACDecoderData *data = (FLACDecoderData*)client_data; + (void)decoder; + + if (data->fatal_error) + return; + + if ( + data->num_metadata_blocks == sizeof(data->metadata_blocks)/sizeof(data->metadata_blocks[0]) || + 0 == (data->metadata_blocks[data->num_metadata_blocks] = FLAC__metadata_object_clone(metadata)) + ) + data->fatal_error = true; + else + data->num_metadata_blocks++; +} + +void flac_decoder_error_callback(const FLAC__SeekableStreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + FLACDecoderData *data = (FLACDecoderData*)client_data; + (void)decoder; + + flac__utils_printf(stderr, 1, "%s: ERROR got %s while decoding FLAC input\n", data->encoder_session->inbasefilename, FLAC__StreamDecoderErrorStatusString[status]); + data->fatal_error = true; +} + +FLAC__bool parse_cuesheet(FLAC__StreamMetadata **cuesheet, const char *cuesheet_filename, const char *inbasefilename, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset) { FILE *f; unsigned last_line_read; @@ -1951,7 +2433,7 @@ void print_error_with_state(const EncoderSession *e, const char *message) "\n" "The encoding parameters specified do not conform to the FLAC Subset and may not\n" "be streamable or playable in hardware devices. Add --lax to the command-line\n" - "options to encode with these parameters.\n" + "options to encode with these parameters anyway.\n" ); } else if( @@ -2010,13 +2492,13 @@ void print_verify_error(EncoderSession *e) flac__utils_printf(stderr, 1, "%s: ERROR: mismatch in decoded data, verify FAILED!\n", e->inbasefilename); flac__utils_printf(stderr, 1, " Absolute sample=%u, frame=%u, channel=%u, sample=%u, expected %d, got %d\n", (unsigned)absolute_sample, frame_number, channel, sample, expected, got); flac__utils_printf(stderr, 1, " In all known cases, verify errors are caused by hardware problems,\n"); - flac__utils_printf(stderr, 1, " usually overclocking or bad RAM. Delete %s\n", e->inbasefilename); + flac__utils_printf(stderr, 1, " usually overclocking or bad RAM. Delete %s\n", e->outfilename); flac__utils_printf(stderr, 1, " and repeat the flac command exactly as before. If it does not give a\n"); flac__utils_printf(stderr, 1, " verify error in the exact same place each time you try it, then there is\n"); flac__utils_printf(stderr, 1, " a problem with your hardware; please see the FAQ:\n"); flac__utils_printf(stderr, 1, " http://flac.sourceforge.net/faq.html#tools__hardware_prob\n"); - flac__utils_printf(stderr, 1, " If it does fail in the exact same place every time, keep the bad FLAC\n"); - flac__utils_printf(stderr, 1, " file and submit a bug report to:\n"); + flac__utils_printf(stderr, 1, " If it does fail in the exact same place every time, keep\n"); + flac__utils_printf(stderr, 1, " %s and submit a bug report to:\n", e->outfilename); flac__utils_printf(stderr, 1, " https://sourceforge.net/bugs/?func=addbug&group_id=13478\n"); flac__utils_printf(stderr, 1, " Make sure to upload the FLAC file and use the \"Monitor\" feature to\n"); flac__utils_printf(stderr, 1, " monitor the bug status.\n"); |