Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FLAC__stream_decoder_find_total_samples #758

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions include/FLAC/stream_decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions oss-fuzz/seek.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
133 changes: 125 additions & 8 deletions src/libFLAC/stream_decoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down
73 changes: 71 additions & 2 deletions src/test_libFLAC/decoders.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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_;
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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:
Expand Down Expand Up @@ -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_();
Expand Down