Improve decoding of chained streams in flac command line tool

This commit is contained in:
Martijn van Beurden 2024-08-23 20:03:38 +02:00
parent 60e9234c6f
commit 08cf8a194a
5 changed files with 156 additions and 76 deletions

View File

@ -38,7 +38,6 @@ typedef struct {
FLAC__bool use_first_serial_number;
long serial_number;
FLAC__bool decode_chained_stream;
uint32_t stream_counter;
#endif
FileFormat format;
@ -87,6 +86,8 @@ typedef struct {
uint32_t sample_rate;
FLAC__uint32 channel_mask;
int stream_counter;
/* these are used only in analyze mode */
FLAC__uint64 decode_position;
FLAC__bool decode_position_valid;
@ -113,6 +114,7 @@ static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg,
static void DecoderSession_destroy(DecoderSession *d, FLAC__bool error_occurred);
static FLAC__bool DecoderSession_init_decoder(DecoderSession *d, const char *infilename);
static FLAC__bool DecoderSession_process(DecoderSession *d);
static FLAC__bool verify_streaminfo(DecoderSession *d, FLAC__bool md5_failure);
static int DecoderSession_finish_ok(DecoderSession *d);
static int DecoderSession_finish_error(DecoderSession *d);
static FLAC__bool canonicalize_until_specification(utils__SkipUntilSpecification *spec, const char *inbasefilename, uint32_t sample_rate, FLAC__uint64 skip, FLAC__uint64 total_samples_in_input);
@ -188,7 +190,7 @@ int flac__decode_file(const char *infilename, const char *outfilename, FLAC__boo
)
return 1;
stats_new_file();
stats_new_line();
if(!DecoderSession_init_decoder(&decoder_session, infilename))
return DecoderSession_finish_error(&decoder_session);
@ -205,7 +207,6 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__
d->use_first_serial_number = use_first_serial_number;
d->serial_number = serial_number;
d->decode_chained_stream = decode_chained_stream;
d->stream_counter = 0;
#else
(void)is_ogg;
(void)use_first_serial_number;
@ -246,6 +247,8 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__
else
d->warn_user_about_foreign_metadata = true;
d->stream_counter = -1;
d->iff_headers_need_fixup = false;
d->total_samples = 0;
@ -381,7 +384,7 @@ FLAC__bool DecoderSession_init_decoder(DecoderSession *decoder_session, const ch
if(decoder_session->is_ogg) {
if(!decoder_session->use_first_serial_number)
FLAC__stream_decoder_set_ogg_serial_number(decoder_session->decoder, decoder_session->serial_number);
FLAC__stream_decoder_set_decode_chained_ogg_stream(decoder_session->decoder, decoder_session->decode_chained_stream);
FLAC__stream_decoder_set_decode_chained_stream(decoder_session->decoder, decoder_session->decode_chained_stream);
init_status = FLAC__stream_decoder_init_ogg_file(decoder_session->decoder, strcmp(infilename, "-")? infilename : 0, write_callback, metadata_callback, error_callback, /*client_data=*/decoder_session);
}
else
@ -491,12 +494,40 @@ FLAC__bool DecoderSession_process(DecoderSession *d)
return false;
}
}
if(!FLAC__stream_decoder_process_until_end_of_stream(d->decoder) && !d->aborting_due_to_until) {
flac__utils_printf(stderr, 2, "\n");
print_error_with_state(d, "ERROR while decoding data");
if(!d->continue_through_decode_errors)
return false;
#if FLAC__HAS_OGG
if(!d->decode_chained_stream) {
#endif
if(!FLAC__stream_decoder_process_until_end_of_stream(d->decoder) && !d->aborting_due_to_until) {
flac__utils_printf(stderr, 2, "\n");
print_error_with_state(d, "ERROR while decoding data");
if(!d->continue_through_decode_errors)
return false;
}
#if FLAC__HAS_OGG
}
else {
FLAC__ASSERT(!d->continue_through_decode_errors);
while(1) {
FLAC__bool md5_failure;
d->stream_counter++;
if(!FLAC__stream_decoder_process_until_end_of_link(d->decoder) && !d->aborting_due_to_until) {
flac__utils_printf(stderr, 2, "\n");
print_error_with_state(d, "ERROR while decoding data");
return false;
}
if(FLAC__stream_decoder_get_state(d->decoder) == FLAC__STREAM_DECODER_END_OF_STREAM)
break;
md5_failure = !FLAC__stream_decoder_finish_link(d->decoder) && !d->aborting_due_to_until;
if(!verify_streaminfo(d, md5_failure))
return false;
/* Reset some stuff for next link */
stats_new_line();
memset(&(d->prev_frameheader), 0, sizeof(FLAC__FrameHeader));
d->samples_processed = 0;
d->got_stream_info = false;
}
}
#endif
if(
(d->abort_flag && !(d->aborting_due_to_until || d->continue_through_decode_errors)) ||
(FLAC__stream_decoder_get_state(d->decoder) > FLAC__STREAM_DECODER_END_OF_STREAM && !d->aborting_due_to_until)
@ -534,9 +565,45 @@ FLAC__bool DecoderSession_process(DecoderSession *d)
return true;
}
FLAC__bool verify_streaminfo(DecoderSession *d, FLAC__bool md5_failure)
{
FLAC__bool ok = true;
if(md5_failure) {
stats_print_name_and_stream_number(1, d->inbasefilename, d->stream_counter);
flac__utils_printf(stderr, 1, "ERROR, MD5 signature mismatch\n");
ok = d->continue_through_decode_errors;
}
else if(d->got_stream_info && d->total_samples && (d->total_samples > d->samples_processed)){
stats_print_name_and_stream_number(1, d->inbasefilename, d->stream_counter);
flac__utils_printf(stderr, 1, "ERROR, decoded number of samples is smaller than the total number of samples set in the STREAMINFO\n");
ok = d->continue_through_decode_errors;
}
else {
if(!d->got_stream_info) {
stats_print_name_and_stream_number(1, d->inbasefilename, d->stream_counter);
flac__utils_printf(stderr, 1, "WARNING, cannot check MD5 signature since there was no STREAMINFO\n");
ok = !d->treat_warnings_as_errors;
}
else if(!d->has_md5sum) {
stats_print_name_and_stream_number(1, d->inbasefilename, d->stream_counter);
flac__utils_printf(stderr, 1, "WARNING, cannot check MD5 signature since it was unset in the STREAMINFO\n");
ok = !d->treat_warnings_as_errors;
}
else if(!d->total_samples) {
stats_print_name_and_stream_number(1, d->inbasefilename, d->stream_counter);
flac__utils_printf(stderr, 1, "WARNING, cannot check total number of samples since it was unset in the STREAMINFO\n");
ok = !d->treat_warnings_as_errors;
}
stats_print_name_and_stream_number(2, d->inbasefilename, d->stream_counter);
flac__utils_printf(stderr, 2, "%s \n", d->test_only? "ok ":d->analysis_mode?"done ":"done");
}
return ok;
}
int DecoderSession_finish_ok(DecoderSession *d)
{
FLAC__bool ok = true, md5_failure = false;
FLAC__bool ok, md5_failure = false;
if(d->decoder) {
md5_failure = !FLAC__stream_decoder_finish(d->decoder) && !d->aborting_due_to_until;
@ -545,41 +612,7 @@ int DecoderSession_finish_ok(DecoderSession *d)
}
if(d->analysis_mode)
flac__analyze_finish(d->aopts);
if(md5_failure) {
stats_print_name(1, d->inbasefilename);
flac__utils_printf(stderr, 1, "ERROR, MD5 signature mismatch\n");
ok = d->continue_through_decode_errors;
}
else if(d->got_stream_info && d->total_samples && (d->total_samples > d->samples_processed)){
stats_print_name(1, d->inbasefilename);
flac__utils_printf(stderr, 1, "ERROR, decoded number of samples is smaller than the total number of samples set in the STREAMINFO\n");
ok = d->continue_through_decode_errors;
}
else {
if(!d->got_stream_info) {
stats_print_name(1, d->inbasefilename);
flac__utils_printf(stderr, 1, "WARNING, cannot check MD5 signature since there was no STREAMINFO\n");
ok = !d->treat_warnings_as_errors;
}
else if(!d->has_md5sum) {
stats_print_name(1, d->inbasefilename);
flac__utils_printf(stderr, 1, "WARNING, cannot check MD5 signature since it was unset in the STREAMINFO\n");
ok = !d->treat_warnings_as_errors;
}
else if(!d->total_samples) {
stats_print_name(1, d->inbasefilename);
flac__utils_printf(stderr, 1, "WARNING, cannot check total number of samples since it was unset in the STREAMINFO\n");
ok = !d->treat_warnings_as_errors;
}
#if FLAC__HAS_OGG
if (d->decode_chained_stream) {
stats_print_name(2, d->inbasefilename);
flac__utils_printf(stderr, 2, "%d stream(s) found\n", d->stream_counter);
}
#endif
stats_print_name(2, d->inbasefilename);
flac__utils_printf(stderr, 2, "%s \n", d->test_only? "ok ":d->analysis_mode?"done ":"done");
}
ok = verify_streaminfo(d, md5_failure);
DecoderSession_destroy(d, /*error_occurred=*/!ok);
if(!d->analysis_mode && !d->test_only && d->format != FORMAT_RAW) {
if(d->iff_headers_need_fixup || (!d->got_stream_info && strcmp(d->outfilename, "-"))) {
@ -1279,7 +1312,9 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
if(decoder_session->prev_frameheader.number.sample_number +
decoder_session->prev_frameheader.blocksize !=
frame->header.number.sample_number) {
flac__utils_printf_clear_stats(stderr, 1, "%s: WARNING: sample or frame number does not increase correctly (%" PRIu64 " samples have been decoded), file might not be seekable\n", decoder_session->inbasefilename, decoder_session->samples_processed);
stats_print_name_and_stream_number(1, decoder_session->inbasefilename, decoder_session->stream_counter);
flac__utils_printf(stderr, 1, "WARNING: sample or frame number does not increase correctly (%" PRIu64 " samples have been decoded), file might not be seekable\n", decoder_session->samples_processed);
stats_new_line();
if(decoder_session->treat_warnings_as_errors) {
decoder_session->abort_flag = true;
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
@ -1512,16 +1547,6 @@ void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMet
(void)decoder;
#if FLAC__HAS_OGG
if (decoder_session->frame_counter && decoder_session->is_ogg && decoder_session->decode_chained_stream) {
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
decoder_session->stream_counter++;
decoder_session->total_samples += metadata->data.stream_info.total_samples;
}
return;
}
#endif
if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
FLAC__uint64 skip, until;
@ -1535,18 +1560,44 @@ void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMet
decoder_session->got_stream_info = true;
decoder_session->has_md5sum = memcmp(metadata->data.stream_info.md5sum, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) != 0;
decoder_session->bps = metadata->data.stream_info.bits_per_sample;
decoder_session->channels = metadata->data.stream_info.channels;
decoder_session->sample_rate = metadata->data.stream_info.sample_rate;
decoder_session->stream_counter = 1;
if(!flac__utils_canonicalize_skip_until_specification(decoder_session->skip_specification, decoder_session->sample_rate)) {
flac__utils_printf(stderr, 1, "%s: ERROR, value of --skip is too large\n", decoder_session->inbasefilename);
decoder_session->abort_flag = true;
return;
if(decoder_session->stream_counter > 0) {
/* This is not the first link in the chain, so check whether parameters are the same */
if(decoder_session->bps != metadata->data.stream_info.bits_per_sample) {
stats_print_name_and_stream_number(1, decoder_session->inbasefilename, decoder_session->stream_counter);
flac__utils_printf(stderr, 1, "ERROR, bits-per-sample is %u in this link's STREAMINFO but was %u in previous one\n", metadata->data.stream_info.bits_per_sample, decoder_session->bps);
decoder_session->abort_flag = true;
return;
}
if(decoder_session->channels != metadata->data.stream_info.channels) {
stats_print_name_and_stream_number(1, decoder_session->inbasefilename, decoder_session->stream_counter);
flac__utils_printf(stderr, 1, "ERROR, channels is %u in this link's STREAMINFO but was %u in previous one\n", metadata->data.stream_info.channels, decoder_session->channels);
decoder_session->abort_flag = true;
return;
}
if(decoder_session->sample_rate != metadata->data.stream_info.sample_rate) {
stats_print_name_and_stream_number(1, decoder_session->inbasefilename, decoder_session->stream_counter);
flac__utils_printf(stderr, 1, "ERROR, sample rate is %u in this link's STREAMINFO but was %u in previous one\n", metadata->data.stream_info.sample_rate, decoder_session->sample_rate);
decoder_session->abort_flag = true;
return;
}
}
else {
decoder_session->bps = metadata->data.stream_info.bits_per_sample;
decoder_session->channels = metadata->data.stream_info.channels;
decoder_session->sample_rate = metadata->data.stream_info.sample_rate;
}
if(decoder_session->stream_counter < 0) {
if(!flac__utils_canonicalize_skip_until_specification(decoder_session->skip_specification, decoder_session->sample_rate)) {
flac__utils_printf(stderr, 1, "%s: ERROR, value of --skip is too large\n", decoder_session->inbasefilename);
decoder_session->abort_flag = true;
return;
}
FLAC__ASSERT(decoder_session->skip_specification->value.samples >= 0);
skip = (FLAC__uint64)decoder_session->skip_specification->value.samples;
}
else
skip = 0;
/* remember, metadata->data.stream_info.total_samples can be 0, meaning 'unknown' */
if(metadata->data.stream_info.total_samples > 0 && skip >= metadata->data.stream_info.total_samples) {
@ -1563,12 +1614,16 @@ void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMet
decoder_session->total_samples = metadata->data.stream_info.total_samples - skip;
/* note that we use metadata->data.stream_info.total_samples instead of decoder_session->total_samples */
if(!canonicalize_until_specification(decoder_session->until_specification, decoder_session->inbasefilename, decoder_session->sample_rate, skip, metadata->data.stream_info.total_samples)) {
decoder_session->abort_flag = true;
return;
if(decoder_session->stream_counter < 0) {
if(!canonicalize_until_specification(decoder_session->until_specification, decoder_session->inbasefilename, decoder_session->sample_rate, skip, metadata->data.stream_info.total_samples)) {
decoder_session->abort_flag = true;
return;
}
FLAC__ASSERT(decoder_session->until_specification->value.samples >= 0);
until = (FLAC__uint64)decoder_session->until_specification->value.samples;
}
FLAC__ASSERT(decoder_session->until_specification->value.samples >= 0);
until = (FLAC__uint64)decoder_session->until_specification->value.samples;
else
until = 0;
if(until > 0) {
FLAC__ASSERT(decoder_session->total_samples != 0);
@ -1741,14 +1796,14 @@ void print_stats(const DecoderSession *decoder_session)
if ((uint32_t)floor(progress + 0.5) == 100)
return;
stats_print_name(2, decoder_session->inbasefilename);
stats_print_name_and_stream_number(2, decoder_session->inbasefilename, decoder_session->stream_counter);
stats_print_info(2, "%s%u%% complete",
decoder_session->test_only? "testing, " : decoder_session->analysis_mode? "analyzing, " : "",
(uint32_t)floor(progress + 0.5)
);
}
else {
stats_print_name(2, decoder_session->inbasefilename);
stats_print_name_and_stream_number(2, decoder_session->inbasefilename, decoder_session->stream_counter);
stats_print_info(2, "%s %" PRIu64 " samples",
decoder_session->test_only? "tested" : decoder_session->analysis_mode? "analyzed" : "wrote",
decoder_session->samples_processed

View File

@ -1119,7 +1119,7 @@ int flac__encode_file(FILE *infile, FLAC__off_t infilesize, const char *infilena
memset(encoder_session.md5sum_input,0,16);
}
stats_new_file();
stats_new_line();
/* init the encoder */
if(!EncoderSession_init_encoder(&encoder_session, options))
return EncoderSession_finish_error(&encoder_session);

View File

@ -375,7 +375,9 @@ int do_it(void)
*/
if(!option_values.mode_decode) {
if(0 != option_values.cue_specification)
return usage_error("ERROR: --cue is not allowed in test mode\n");
return usage_error("ERROR: --cue must be used together with -d\n");
if(0 != option_values.decode_chained_stream)
return usage_error("ERROR: --decode-chained-streams must be used together with -d, -t or -a\n");
}
else {
if(option_values.test_only) {
@ -475,6 +477,18 @@ int do_it(void)
return usage_error("ERROR: --keep-foreign-metadata is not allowed in analyis mode\n");
flac__utils_printf(stderr, 1, "NOTE: --keep-foreign-metadata is a new feature; make sure to test the output file before deleting the original.\n");
}
if(0 != option_values.decode_chained_stream) {
if(0 != option_values.skip_specification)
return usage_error("ERROR: --skip is not supported when decoding chained streams\n");
if(0 != option_values.until_specification)
return usage_error("ERROR: --until is not supported when decoding chained streams\n");
if(0 != option_values.cue_specification)
return usage_error("ERROR: --cue is not supported when decoding chained streams\n");
if(option_values.continue_through_decode_errors)
return usage_error("ERROR: decoding through errors is not supported when decoding chained streams\n");
}
}
flac__utils_printf(stderr, 2, "\n");

View File

@ -227,7 +227,7 @@ size_t strlen_console(const char *text)
#endif
}
void stats_new_file(void)
void stats_new_line(void)
{
is_name_printed = false;
stats_char_count = 0;
@ -240,6 +240,11 @@ void stats_clear(void)
}
void stats_print_name(int level, const char *name)
{
stats_print_name_and_stream_number(level, name, -1);
}
void stats_print_name_and_stream_number(int level, const char *name, int stream_number)
{
int len;
@ -249,8 +254,13 @@ void stats_print_name(int level, const char *name)
console_width = get_console_width();
len = strlen_console(name)+2;
if(stream_number >= 0)
len += 10 + floor(log10(stream_number));
console_chars_left = console_width - (len % console_width);
flac_fprintf(stderr, "%s: ", name);
if(stream_number < 0)
flac_fprintf(stderr, "%s: ", name);
else
flac_fprintf(stderr, "%s, stream %d: ", name, stream_number);
is_name_printed = true;
}
}

View File

@ -60,9 +60,10 @@ void flac__utils_printf(FILE *stream, int level, const char *format, ...);
int get_console_width(void);
size_t strlen_console(const char *text);
void stats_new_file(void);
void stats_new_line(void);
void stats_clear(void);
void stats_print_name(int level, const char *name);
void stats_print_name_and_stream_number(int level, const char *name, int stream_number);
void stats_print_info(int level, const char *format, ...);
void flac__utils_printf_clear_stats(FILE *stream, int level, const char *format, ...);