From 17811b3af605b25a10393547479dd1424ae0ccd1 Mon Sep 17 00:00:00 2001 From: Martijn van Beurden Date: Wed, 13 Nov 2024 09:21:37 +0100 Subject: [PATCH] Add FLAC__stream_decoder_find_total_samples (#758) --- include/FLAC/stream_decoder.h | 26 +++++++ oss-fuzz/seek.cc | 14 ++++ src/libFLAC/stream_decoder.c | 133 ++++++++++++++++++++++++++++++++-- src/test_libFLAC/decoders.c | 73 ++++++++++++++++++- 4 files changed, 236 insertions(+), 10 deletions(-) diff --git a/include/FLAC/stream_decoder.h b/include/FLAC/stream_decoder.h index 7232802a56..be6c356ab4 100644 --- a/include/FLAC/stream_decoder.h +++ b/include/FLAC/stream_decoder.h @@ -995,6 +995,32 @@ FLAC_API FLAC__bool FLAC__stream_decoder_get_md5_checking(const FLAC__StreamDeco */ FLAC_API FLAC__uint64 FLAC__stream_decoder_get_total_samples(const FLAC__StreamDecoder *decoder); +/** Seek to the end of the file being decoded to find the total number + * of samples. This will return a number of samples even if + * FLAC__stream_decoder_get_total_samples() returns 0. It can also + * be used to find the total number of samples of a chained stream, + * as it returns the total number of samples in all chain links + * combined. See FLAC__stream_decoder_set_decode_chained_stream() + * + * For this function to work, the stream must be seekable. Also, as + * seeking can fail, this function returns 0 when it was unable to + * find the total number of samples. Use + * FLAC__stream_decoder_get_state() in that case to find out whether + * the decoder is still valid. + * + * The state in which the decoder is left after calling this function + * is undefined and might change in the future. Use + * FLAC__stream_decoder_reset() to return the decoder to a defined + * state. + * + * \param decoder A decoder instance to query. + * \assert + * \code decoder != NULL \endcode + * \retval uint32_t + * See above. + */ +FLAC_API FLAC__uint64 FLAC__stream_decoder_find_total_samples(FLAC__StreamDecoder *decoder); + /** Get the current number of channels in the stream being decoded. * Will only be valid after decoding has started and will contain the * value from the most recently decoded frame header. diff --git a/oss-fuzz/seek.cc b/oss-fuzz/seek.cc index b787dc69a8..a366202ffb 100644 --- a/oss-fuzz/seek.cc +++ b/oss-fuzz/seek.cc @@ -195,9 +195,23 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) FPRINTF_DEBUG_ONLY(stderr,"finish_link\n"); if(FLAC__stream_decoder_get_state(decoder) == FLAC__STREAM_DECODER_END_OF_LINK) FLAC__stream_decoder_finish_link(decoder); + break; case 11: FPRINTF_DEBUG_ONLY(stderr,"skip_single_link\n"); decoder_valid = FLAC__stream_decoder_skip_single_link(decoder); + break; + case 12: + FPRINTF_DEBUG_ONLY(stderr,"find_total_samples\n"); + if(FLAC__stream_decoder_find_total_samples(decoder) == 0) { + FLAC__StreamDecoderState state = FLAC__stream_decoder_get_state(decoder); + if(state == FLAC__STREAM_DECODER_OGG_ERROR || + state == FLAC__STREAM_DECODER_SEEK_ERROR || + state == FLAC__STREAM_DECODER_ABORTED || + state == FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR || + state == FLAC__STREAM_DECODER_UNINITIALIZED) + decoder_valid = false; + } + break; } } diff --git a/src/libFLAC/stream_decoder.c b/src/libFLAC/stream_decoder.c index 5f21719395..7d8c575034 100644 --- a/src/libFLAC/stream_decoder.c +++ b/src/libFLAC/stream_decoder.c @@ -167,7 +167,7 @@ typedef struct FLAC__StreamDecoderPrivate { FLAC__uint64 last_seen_framesync; /* if tell callback works, the location of the last seen frame sync code, to rewind to if needed */ FLAC__uint64 target_sample; uint32_t unparseable_frame_count; /* used to tell whether we're decoding a future version of FLAC or just got a bad sync */ - FLAC__bool got_a_frame; /* hack needed in Ogg FLAC seek routine to check when process_single() actually writes a frame */ + FLAC__bool got_a_frame; /* hack needed in Ogg FLAC seek routine and find_total_samples to check when process_single() actually writes a frame */ FLAC__bool (*local_bitreader_read_rice_signed_block)(FLAC__BitReader *br, int vals[], uint32_t nvals, uint32_t parameter); FLAC__bool error_has_been_sent; /* To check whether a missing frame has been signalled yet */ } FLAC__StreamDecoderPrivate; @@ -1338,6 +1338,129 @@ FLAC_API FLAC__bool FLAC__stream_decoder_seek_absolute(FLAC__StreamDecoder *deco } } +FLAC_API FLAC__uint64 FLAC__stream_decoder_find_total_samples(FLAC__StreamDecoder *decoder) +{ + if( + decoder->protected_->state != FLAC__STREAM_DECODER_SEARCH_FOR_METADATA && + decoder->protected_->state != FLAC__STREAM_DECODER_READ_METADATA && + decoder->protected_->state != FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC && + decoder->protected_->state != FLAC__STREAM_DECODER_READ_FRAME && + decoder->protected_->state != FLAC__STREAM_DECODER_END_OF_STREAM + ) + return 0; + + if( + decoder->private_->length_callback == NULL || + decoder->private_->seek_callback == NULL || + decoder->private_->tell_callback == NULL + ) + return 0; + +#if FLAC__HAS_OGG + if(decoder->private_->is_ogg && FLAC__ogg_decoder_aspect_get_decode_chained_stream(&decoder->protected_->ogg_decoder_aspect)) { + /* Keep moving forward until reaching end-of-stream */ + uint32_t i; + FLAC__uint64 total_samples = 0; + decoder->private_->is_indexing = true; + while(1) { + FLAC__OggDecoderAspectReadStatus status; + if(decoder->protected_->state == FLAC__STREAM_DECODER_END_OF_STREAM || + decoder->protected_->state == FLAC__STREAM_DECODER_OGG_ERROR || + decoder->protected_->state == FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR || + decoder->protected_->state == FLAC__STREAM_DECODER_ABORTED) { + decoder->private_->is_indexing = false; + decoder->protected_->state = FLAC__STREAM_DECODER_SEEK_ERROR; + return 0; + } + status = FLAC__ogg_decoder_aspect_skip_link(&decoder->protected_->ogg_decoder_aspect, read_callback_proxy_, decoder->private_->seek_callback, decoder->private_->tell_callback, decoder->private_->length_callback, decoder, decoder->private_->client_data); + if(status == FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM) + break; + else if(status != FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK) { + decoder->protected_->state = FLAC__STREAM_DECODER_SEEK_ERROR; + return 0; + } + } + decoder->private_->is_indexing = false; + for(i = 0; i < decoder->protected_->ogg_decoder_aspect.number_of_links_indexed; i++) { + total_samples += decoder->protected_->ogg_decoder_aspect.linkdetails[i].samples; + } + return total_samples; + } + else +#endif /* FLAC__HAS_OGG */ + { /* not decoding chained ogg */ + FLAC__uint64 length; + FLAC__uint64 pos; + uint32_t eof_distance = 1024; /* Some number, needs tuning */ + decoder->private_->is_seeking = true; + decoder->private_->target_sample = UINT64_MAX; + /* get the file length */ + if(decoder->private_->length_callback(decoder, &length, decoder->private_->client_data) != FLAC__STREAM_DECODER_LENGTH_STATUS_OK) { + decoder->private_->is_indexing = false; + return 0; + } + pos = length; + for( ; ; eof_distance *= 2) { + if(eof_distance > (1u << FLAC__STREAM_METADATA_LENGTH_LEN)) { + /* Could not find a frame within a reasonable distance of EOF */ + return 0; + } + else if(pos == 0) { + /* Did not find a frame while reading from the start of the file */ + return 0; + } + else if(eof_distance > length) { + pos = 0; + } + else { + pos = length - eof_distance; + } + + if(decoder->private_->seek_callback(decoder, pos, decoder->private_->client_data) != FLAC__STREAM_DECODER_SEEK_STATUS_OK) { + decoder->protected_->state = FLAC__STREAM_DECODER_SEEK_ERROR; + return 0; + } + if(!FLAC__stream_decoder_flush(decoder)) { + /* above call sets the state for us */ + return 0; + } + + decoder->private_->got_a_frame = false; + if(!FLAC__stream_decoder_process_single(decoder) || + decoder->protected_->state == FLAC__STREAM_DECODER_ABORTED) { + decoder->protected_->state = FLAC__STREAM_DECODER_SEEK_ERROR; + return 0; + } + if(decoder->private_->got_a_frame) { + /* Found a frame, but we need the last frame and the frame before that, unless the last frame is + * also the first frame */ + if(decoder->private_->frame.header.number.sample_number > 0) { + /* For now, assume this is not the last frame, set blocksize, and continue. + * If it turns out this is not the last frame, we'll start over anyway */ + decoder->private_->fixed_block_size = decoder->private_->last_frame.header.blocksize; + if(!FLAC__stream_decoder_process_single(decoder) || + decoder->protected_->state == FLAC__STREAM_DECODER_ABORTED) { + decoder->protected_->state = FLAC__STREAM_DECODER_SEEK_ERROR; + return 0; + } + if(decoder->protected_->state == FLAC__STREAM_DECODER_END_OF_STREAM) { + /* Found last frame, but need to find frame before that too */ + continue; + } + } + if(!FLAC__stream_decoder_process_until_end_of_stream(decoder)) + return 0; + FLAC__ASSERT(decoder->private_->is_seeking); + FLAC__ASSERT(decoder->private_->last_frame_is_set); + decoder->private_->is_seeking = false; + return (FLAC__uint64)decoder->private_->last_frame.header.number.sample_number + (FLAC__uint64)decoder->private_->last_frame.header.blocksize; + + } + } + } + return 0; +} + /*********************************************************************** * * Protected class methods @@ -3395,9 +3518,8 @@ FLAC__StreamDecoderWriteStatus write_audio_frame_to_client_(FLAC__StreamDecoder FLAC__ASSERT(frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER); -#if FLAC__HAS_OGG decoder->private_->got_a_frame = true; -#endif + if(this_frame_sample <= target_sample && target_sample < next_frame_sample) { /* we hit our target frame */ uint32_t delta = (uint32_t)(target_sample - this_frame_sample); /* kick out of seek mode */ @@ -3748,11 +3870,6 @@ FLAC__bool seek_to_absolute_sample_ogg_(FLAC__StreamDecoder *decoder, FLAC__uint decoder->protected_->state = FLAC__STREAM_DECODER_SEEK_ERROR; return false; } -/* - FLAC__stream_decoder_process_until_end_of_link(decoder); - if(decoder->protected_->state == FLAC__STREAM_DECODER_END_OF_LINK) - FLAC__stream_decoder_finish_link(decoder); -*/ status = FLAC__ogg_decoder_aspect_skip_link(&decoder->protected_->ogg_decoder_aspect, read_callback_proxy_, decoder->private_->seek_callback, decoder->private_->tell_callback, decoder->private_->length_callback, decoder, decoder->private_->client_data); if(status == FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM) decoder->protected_->state = FLAC__STREAM_DECODER_END_OF_STREAM; diff --git a/src/test_libFLAC/decoders.c b/src/test_libFLAC/decoders.c index b1593c0db0..95f444ac7f 100644 --- a/src/test_libFLAC/decoders.c +++ b/src/test_libFLAC/decoders.c @@ -65,6 +65,7 @@ static FLAC__StreamMetadata *expected_metadata_sequence_other_chain_[3]; static uint32_t num_expected_; static uint32_t num_expected_other_chain_; static FLAC__off_t flacfilesize_; +static uint32_t samples_; static const char *flacfilename(FLAC__bool is_ogg, FLAC__bool is_chained_ogg) { @@ -118,7 +119,7 @@ static FLAC__bool generate_file_(FLAC__bool is_ogg, FLAC__bool is_chained_ogg) expected_metadata_sequence_other_chain_[0] = &streaminfo_; expected_metadata_sequence_other_chain_[1] = &vorbiscomment_; expected_metadata_sequence_other_chain_[2] = &unknown_; - if(!file_utils__generate_flacfile(is_ogg, flacfilename(is_ogg,true), &flacfilesize_, 512 * 1024, &streaminfo_, expected_metadata_sequence_other_chain_+1, 2)) + if(!file_utils__generate_flacfile(is_ogg, flacfilename(is_ogg,true), &flacfilesize_, samples_, &streaminfo_, expected_metadata_sequence_other_chain_+1, 2)) return die_("creating the encoded file"); file_utils__ogg_serial_number++; filesize=flacfilesize_; @@ -135,7 +136,7 @@ static FLAC__bool generate_file_(FLAC__bool is_ogg, FLAC__bool is_chained_ogg) expected_metadata_sequence_[num_expected_++] = &unknown_; /* WATCHOUT: for Ogg FLAC the encoder should move the VORBIS_COMMENT block to the front, right after STREAMINFO */ - if(!file_utils__generate_flacfile(is_ogg, flacfilename(is_ogg,false), &flacfilesize_, 512 * 1024, &streaminfo_, expected_metadata_sequence_, num_expected_)) + if(!file_utils__generate_flacfile(is_ogg, flacfilename(is_ogg,false), &flacfilesize_, samples_, &streaminfo_, expected_metadata_sequence_, num_expected_)) return die_("creating the encoded file"); if(is_chained_ogg) { @@ -482,6 +483,7 @@ static FLAC__bool test_stream_decoder(Layer layer, FLAC__bool is_ogg, FLAC__bool FLAC__StreamDecoderState state; StreamDecoderClientData decoder_client_data; FLAC__bool expect; + FLAC__uint64 total_samples; decoder_client_data.layer = layer; decoder_client_data.other_chain = false; @@ -508,6 +510,72 @@ static FLAC__bool test_stream_decoder(Layer layer, FLAC__bool is_ogg, FLAC__bool } printf("OK\n"); + if(layer < LAYER_FILENAME) { + printf("opening %sFLAC file... ", is_ogg? "Ogg ":""); + open_test_file(&decoder_client_data, is_ogg, is_chained_ogg, "rb"); + if(0 == decoder_client_data.file) { + printf("ERROR (%s)\n", strerror(errno)); + return false; + } + printf("OK\n"); + } + + if(is_chained_ogg) { + printf("testing FLAC__stream_decoder_set_decode_chained_stream()... "); + if(!FLAC__stream_decoder_set_decode_chained_stream(decoder, true)) + return die_s_("returned false", decoder); + printf("OK\n"); + } + + switch(layer) { + case LAYER_STREAM: + printf("testing FLAC__stream_decoder_init_%sstream()... ", is_ogg? "ogg_":""); + init_status = is_ogg? + FLAC__stream_decoder_init_ogg_stream(decoder, stream_decoder_read_callback_, /*seek_callback=*/0, /*tell_callback=*/0, /*length_callback=*/0, /*eof_callback=*/0, stream_decoder_write_callback_, stream_decoder_metadata_callback_, stream_decoder_error_callback_, &decoder_client_data) : + FLAC__stream_decoder_init_stream(decoder, stream_decoder_read_callback_, /*seek_callback=*/0, /*tell_callback=*/0, /*length_callback=*/0, /*eof_callback=*/0, stream_decoder_write_callback_, stream_decoder_metadata_callback_, stream_decoder_error_callback_, &decoder_client_data); + break; + case LAYER_SEEKABLE_STREAM: + printf("testing FLAC__stream_decoder_init_%sstream()... ", is_ogg? "ogg_":""); + init_status = is_ogg? + FLAC__stream_decoder_init_ogg_stream(decoder, stream_decoder_read_callback_, stream_decoder_seek_callback_, stream_decoder_tell_callback_, stream_decoder_length_callback_, stream_decoder_eof_callback_, stream_decoder_write_callback_, stream_decoder_metadata_callback_, stream_decoder_error_callback_, &decoder_client_data) : + FLAC__stream_decoder_init_stream(decoder, stream_decoder_read_callback_, stream_decoder_seek_callback_, stream_decoder_tell_callback_, stream_decoder_length_callback_, stream_decoder_eof_callback_, stream_decoder_write_callback_, stream_decoder_metadata_callback_, stream_decoder_error_callback_, &decoder_client_data); + break; + case LAYER_FILE: + printf("testing FLAC__stream_decoder_init_%sFILE()... ", is_ogg? "ogg_":""); + init_status = is_ogg? + FLAC__stream_decoder_init_ogg_FILE(decoder, decoder_client_data.file, stream_decoder_write_callback_, stream_decoder_metadata_callback_, stream_decoder_error_callback_, &decoder_client_data) : + FLAC__stream_decoder_init_FILE(decoder, decoder_client_data.file, stream_decoder_write_callback_, stream_decoder_metadata_callback_, stream_decoder_error_callback_, &decoder_client_data); + break; + case LAYER_FILENAME: + printf("testing FLAC__stream_decoder_init_%sfile()... ", is_ogg? "ogg_":""); + init_status = is_ogg? + FLAC__stream_decoder_init_ogg_file(decoder, flacfilename(is_ogg,is_chained_ogg), stream_decoder_write_callback_, stream_decoder_metadata_callback_, stream_decoder_error_callback_, &decoder_client_data) : + FLAC__stream_decoder_init_file(decoder, flacfilename(is_ogg,is_chained_ogg), stream_decoder_write_callback_, stream_decoder_metadata_callback_, stream_decoder_error_callback_, &decoder_client_data); + break; + default: + die_("internal error 009"); + return false; + } + if(init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) + return die_s_(0, decoder); + printf("OK\n"); + + printf("testing FLAC__stream_decoder_find_total_samples... "); + total_samples = FLAC__stream_decoder_find_total_samples(decoder); + printf("Number of samples returned is %" PRIu64 "... ",total_samples); + if((layer == LAYER_STREAM && total_samples != 0) || + (layer != LAYER_STREAM && is_chained_ogg && total_samples != (samples_ * 2)) || + (layer != LAYER_STREAM && !is_chained_ogg && total_samples != samples_)) + return die_s_("returned wrong number of samples", decoder); + printf("OK\n"); + + if(layer < LAYER_FILE) /* for LAYER_FILE, FLAC__stream_decoder_finish() closes the file */ + fclose(decoder_client_data.file); + + printf("testing FLAC__stream_decoder_finish()... "); + FLAC__stream_decoder_finish(decoder); + printf("OK\n"); + switch(layer) { case LAYER_STREAM: case LAYER_SEEKABLE_STREAM: @@ -1327,6 +1395,7 @@ FLAC__bool test_decoders(void) { FLAC__bool is_ogg = false; FLAC__bool is_chained_ogg = false; + samples_ = 1024 * 512; while(1) { init_metadata_blocks_();