Skip to content

Commit

Permalink
Add FLAC__stream_decoder_find_total_samples (#758)
Browse files Browse the repository at this point in the history
  • Loading branch information
ktmf01 authored Nov 13, 2024
1 parent ecbac1f commit 17811b3
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 10 deletions.
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

0 comments on commit 17811b3

Please sign in to comment.