/* libFLAC - Free Lossless Audio Codec
* Copyright (C) 2002-2009 Josh Coalson
* Copyright (C) 2011-2025 Xiph.Org Foundation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the Xiph.org Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h> /* for memcpy() */
#include "FLAC/assert.h"
#include "share/alloc.h" /* for free() */
#include "private/ogg_decoder_aspect.h"
#include "private/ogg_mapping.h"
#include "private/macros.h"
static FLAC__OggDecoderAspectReadStatus read_more_data_(FLAC__OggDecoderAspect *aspect, FLAC__OggDecoderAspectReadCallbackProxy read_callback, size_t bytes_requested, const FLAC__StreamDecoder *decoder, void *client_data);
/***********************************************************************
*
* Public class methods
*
***********************************************************************/
static FLAC__OggDecoderAspectReadStatus read_more_data_(FLAC__OggDecoderAspect *aspect, FLAC__OggDecoderAspectReadCallbackProxy read_callback, size_t bytes_requested, const FLAC__StreamDecoder *decoder, void *client_data)
{
static const size_t OGG_BYTES_CHUNK = 8192;
const size_t ogg_bytes_to_read = flac_max(bytes_requested, OGG_BYTES_CHUNK);
char *oggbuf = ogg_sync_buffer(&aspect->sync_state, ogg_bytes_to_read);
if(0 == oggbuf) {
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
}
else {
size_t ogg_bytes_read = ogg_bytes_to_read;
switch(read_callback(decoder, (FLAC__byte*)oggbuf, &ogg_bytes_read, client_data)) {
case FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK:
break;
case FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM:
aspect->end_of_stream = true;
break;
case FLAC__OGG_DECODER_ASPECT_READ_STATUS_ABORT:
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ABORT;
default:
FLAC__ASSERT(0);
}
if(ogg_sync_wrote(&aspect->sync_state, ogg_bytes_read) < 0) {
/* double protection; this will happen if the read callback returns more bytes than the max requested, which would overflow Ogg's internal buffer */
FLAC__ASSERT(0);
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
}
}
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
}
static FLAC__OggDecoderAspectReadStatus process_page_(FLAC__OggDecoderAspect *aspect, FLAC__StreamDecoderTellCallback tell_callback, const FLAC__StreamDecoder *decoder, void *client_data)
{
/* got a page, grab the serial number if necessary */
if(aspect->need_serial_number) {
/* Check whether not FLAC. The next if is somewhat confusing: check
* whether the length of the next page body agrees with the length
* of a FLAC 'header' possibly contained in that page */
if(aspect->working_page.body_len > (long)(1 + FLAC__OGG_MAPPING_MAGIC_LENGTH) &&
aspect->working_page.body[0] == FLAC__OGG_MAPPING_FIRST_HEADER_PACKET_TYPE &&
memcmp((&aspect->working_page.body) + 1, FLAC__OGG_MAPPING_MAGIC, FLAC__OGG_MAPPING_MAGIC_LENGTH)) {
aspect->bos_flag_seen = true;
aspect->serial_number = ogg_page_serialno(&aspect->working_page);
ogg_stream_reset_serialno(&aspect->stream_state, aspect->serial_number);
aspect->need_serial_number = false;
if(aspect->current_linknumber_advance_read >= aspect->number_of_links_detected) {
FLAC__uint64 tell_offset;
aspect->number_of_links_detected = aspect->current_linknumber_advance_read + 1;
aspect->linkdetails[aspect->current_linknumber_advance_read].serial_number = aspect->serial_number;
if(tell_callback != 0) {
if(tell_callback(decoder, &tell_offset, client_data) == FLAC__STREAM_DECODER_TELL_STATUS_OK)
aspect->linkdetails[aspect->current_linknumber_advance_read].start_byte = tell_offset - aspect->sync_state.fill + aspect->sync_state.returned
- aspect->working_page.header_len - aspect->working_page.body_len;
}
}
}
}
if(aspect->beginning_of_link) {
if(aspect->bos_flag_seen && !ogg_page_bos(&aspect->working_page)) {
/* Page does not have BOS flag, which means we're done scanning for other serial numbers */
aspect->beginning_of_link = false;
}
}
if(ogg_stream_pagein(&aspect->stream_state, &aspect->working_page) == 0) {
aspect->have_working_page = true;
aspect->have_working_packet = false;
}
else if(aspect->beginning_of_link) {
/* At the beginning of a link, store the serial numbers of all other streams, to make
* finding the end of a link through seeking possible */
if(ogg_page_bos(&aspect->working_page)) {
aspect->bos_flag_seen = true;
if(aspect->current_linknumber_advance_read >= aspect->number_of_links_indexed) {
FLAC__OggDecoderAspect_LinkDetails * current_link = &aspect->linkdetails[aspect->current_linknumber_advance_read];
/* Reallocate in chunks of 4 */
if((current_link->number_of_other_streams) % 4 == 0) {
long * tmpptr = NULL;
if(NULL == (tmpptr = safe_realloc_nofree_mul_2op_(current_link->other_serial_numbers, 4+current_link->number_of_other_streams, sizeof(long)))) {
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
}
current_link->other_serial_numbers = tmpptr;
}
current_link->other_serial_numbers[current_link->number_of_other_streams] = ogg_page_serialno(&aspect->working_page);
current_link->number_of_other_streams++;
}
}
/* No BOS flag seen yet, these pages might be still from previous link */
}
/* else do nothing, could be a page from another stream */
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
}
static FLAC__bool check_size_of_link_allocation_(FLAC__OggDecoderAspect *aspect)
{
/* double on reallocating */
if(aspect->current_linknumber >= aspect->number_of_links_allocated || aspect->current_linknumber_advance_read >= aspect->number_of_links_allocated) {
FLAC__OggDecoderAspect_LinkDetails * tmpptr = NULL;
if(NULL == (tmpptr = safe_realloc_nofree_mul_2op_(aspect->linkdetails,2*aspect->number_of_links_allocated,sizeof(FLAC__OggDecoderAspect_LinkDetails)))) {
return false;
}
aspect->linkdetails = tmpptr;
memset(aspect->linkdetails + aspect->number_of_links_allocated, 0, aspect->number_of_links_allocated * sizeof(FLAC__OggDecoderAspect_LinkDetails));
aspect->number_of_links_allocated *= 2;
}
return true;
}
FLAC__bool FLAC__ogg_decoder_aspect_init(FLAC__OggDecoderAspect *aspect)
{
/* we will determine the serial number later if necessary */
if(ogg_stream_init(&aspect->stream_state, aspect->serial_number) != 0)
return false;
if(ogg_sync_init(&aspect->sync_state) != 0)
return false;
aspect->version_major = ~(0u);
aspect->version_minor = ~(0u);
aspect->need_serial_number = aspect->use_first_serial_number || aspect->decode_chained_stream;
aspect->end_of_stream = false;
aspect->have_working_page = false;
aspect->end_of_link = false;
aspect->current_linknumber = 0;
aspect->current_linknumber_advance_read = 0;
aspect->number_of_links_indexed = 0;
aspect->number_of_links_detected = 0;
aspect->number_of_links_allocated = 0;
if(NULL == (aspect->linkdetails = safe_realloc_mul_2op_(NULL,4,sizeof(FLAC__OggDecoderAspect_LinkDetails))))
return false;
memset(aspect->linkdetails, 0, 4 * sizeof(FLAC__OggDecoderAspect_LinkDetails));
aspect->number_of_links_allocated = 4;
return true;
}
void FLAC__ogg_decoder_aspect_finish(FLAC__OggDecoderAspect *aspect)
{
uint32_t i;
(void)ogg_sync_clear(&aspect->sync_state);
(void)ogg_stream_clear(&aspect->stream_state);
if(NULL != aspect->linkdetails) {
for(i = 0; i < aspect->number_of_links_allocated; i++)
free(aspect->linkdetails[i].other_serial_numbers);
free(aspect->linkdetails);
}
aspect->linkdetails = NULL;
}
void FLAC__ogg_decoder_aspect_set_serial_number(FLAC__OggDecoderAspect *aspect, long value)
{
aspect->use_first_serial_number = false;
aspect->serial_number = value;
}
void FLAC__ogg_decoder_aspect_set_defaults(FLAC__OggDecoderAspect *aspect)
{
aspect->use_first_serial_number = true;
aspect->decode_chained_stream = false;
}
void FLAC__ogg_decoder_aspect_flush(FLAC__OggDecoderAspect *aspect)
{
(void)ogg_stream_reset(&aspect->stream_state);
(void)ogg_sync_reset(&aspect->sync_state);
aspect->end_of_stream = false;
aspect->have_working_page = false;
aspect->end_of_link = false;
}
void FLAC__ogg_decoder_aspect_reset(FLAC__OggDecoderAspect *aspect)
{
FLAC__ogg_decoder_aspect_flush(aspect);
aspect->current_linknumber = 0;
aspect->current_linknumber_advance_read = 0;
if(aspect->use_first_serial_number || aspect->decode_chained_stream)
aspect->need_serial_number = true;
aspect->beginning_of_link = true;
aspect->bos_flag_seen = false;
}
void FLAC__ogg_decoder_aspect_next_link(FLAC__OggDecoderAspect* aspect)
{
aspect->end_of_link = false;
aspect->current_linknumber++;
aspect->beginning_of_link = true;
aspect->bos_flag_seen = false;
}
void FLAC__ogg_decoder_aspect_set_decode_chained_stream(FLAC__OggDecoderAspect* aspect, FLAC__bool value)
{
aspect->decode_chained_stream = value;
}
FLAC__bool FLAC__ogg_decoder_aspect_get_decode_chained_stream(FLAC__OggDecoderAspect* aspect)
{
return aspect->decode_chained_stream;
}
FLAC__OggDecoderAspect_TargetLink * FLAC__ogg_decoder_aspect_get_target_link(FLAC__OggDecoderAspect* aspect, FLAC__uint64 target_sample)
{
/* This returns the link containing the seek target if known. In
* effect, this function always returns NULL if no links have been
* indexed */
uint32_t current_link = 0;
uint32_t total_samples = 0;
while(current_link < aspect->number_of_links_indexed) {
total_samples += aspect->linkdetails[current_link].samples;
if(target_sample < total_samples) {
aspect->target_link.serial_number = aspect->linkdetails[current_link].serial_number;
aspect->target_link.start_byte = aspect->linkdetails[current_link].start_byte;
aspect->target_link.samples_in_preceding_links = total_samples - aspect->linkdetails[current_link].samples;
aspect->target_link.end_byte = aspect->linkdetails[current_link].end_byte;
aspect->target_link.samples_this_link = aspect->linkdetails[current_link].samples;
aspect->target_link.linknumber = current_link;
return &aspect->target_link;
}
current_link++;
}
return NULL;
}
void FLAC__ogg_decoder_aspect_set_seek_parameters(FLAC__OggDecoderAspect *aspect, FLAC__OggDecoderAspect_TargetLink *target_link)
{
if(target_link == 0) {
aspect->is_seeking = false;
}
else {
aspect->need_serial_number = false;
aspect->current_linknumber = target_link->linknumber;
aspect->current_linknumber_advance_read = target_link->linknumber;
aspect->serial_number = target_link->serial_number;
ogg_stream_reset_serialno(&aspect->stream_state, aspect->serial_number);
aspect->is_seeking = true;
}
}
FLAC__OggDecoderAspectReadStatus FLAC__ogg_decoder_aspect_read_callback_wrapper(FLAC__OggDecoderAspect *aspect, FLAC__byte buffer[], size_t *bytes, FLAC__OggDecoderAspectReadCallbackProxy read_callback, FLAC__StreamDecoderTellCallback tell_callback, const FLAC__StreamDecoder *decoder, void *client_data)
{
const size_t bytes_requested = *bytes;
const uint32_t header_length =
FLAC__OGG_MAPPING_PACKET_TYPE_LENGTH +
FLAC__OGG_MAPPING_MAGIC_LENGTH +
FLAC__OGG_MAPPING_VERSION_MAJOR_LENGTH +
FLAC__OGG_MAPPING_VERSION_MINOR_LENGTH +
FLAC__OGG_MAPPING_NUM_HEADERS_LENGTH;
/*
* The FLAC decoding API uses pull-based reads, whereas Ogg decoding
* is push-based. In libFLAC, when you ask to decode a frame, the
* decoder will eventually call the read callback to supply some data,
* but how much it asks for depends on how much free space it has in
* its internal buffer. It does not try to grow its internal buffer
* to accommodate a whole frame because then the internal buffer size
* could not be limited, which is necessary in embedded applications.
*
* Ogg however grows its internal buffer until a whole page is present;
* only then can you get decoded data out. So we can't just ask for
* the same number of bytes from Ogg, then pass what's decoded down to
* libFLAC. If what libFLAC is asking for will not contain a whole
* page, then we will get no data from ogg_sync_pageout(), and at the
* same time cannot just read more data from the client for the purpose
* of getting a whole decoded page because the decoded size might be
* larger than libFLAC's internal buffer.
*
* Instead, whenever this read callback wrapper is called, we will
* continually request data from the client until we have at least one
* page, and manage pages internally so that we can send pieces of
* pages down to libFLAC in such a way that we obey its size
* requirement. To limit the amount of callbacks, we will always try
* to read in enough pages to return the full number of bytes
* requested.
*/
*bytes = 0;
while (*bytes < bytes_requested && !aspect->end_of_stream) {
if (aspect->end_of_link && aspect->have_working_page) {
/* we've now consumed all packets of this link and have checked that a new page follows it */
if(*bytes > 0)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
else
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_LINK;
}
else if (aspect->have_working_page) {
if (aspect->have_working_packet) {
size_t n = bytes_requested - *bytes;
if ((size_t)aspect->working_packet.bytes <= n) {
/* the rest of the packet will fit in the buffer */
n = aspect->working_packet.bytes;
memcpy(buffer, aspect->working_packet.packet, n);
*bytes += n;
buffer += n;
aspect->have_working_packet = false;
if(aspect->working_packet.e_o_s) {
if(!aspect->decode_chained_stream)
aspect->end_of_stream = true;
else {
aspect->end_of_link = true;
aspect->current_linknumber_advance_read = aspect->current_linknumber + 1;
if(!check_size_of_link_allocation_(aspect))
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
if(aspect->current_linknumber >= aspect->number_of_links_indexed) {
FLAC__uint64 tell_offset;
FLAC__ASSERT(aspect->current_linknumber == aspect->number_of_links_indexed);
aspect->linkdetails[aspect->current_linknumber].samples = aspect->working_packet.granulepos;
if(tell_callback != 0) {
if(tell_callback(decoder, &tell_offset, client_data) == FLAC__STREAM_DECODER_TELL_STATUS_OK)
aspect->linkdetails[aspect->current_linknumber].end_byte = tell_offset - aspect->sync_state.fill + aspect->sync_state.returned;
}
aspect->number_of_links_indexed++;
aspect->need_serial_number = true;
}
if(!aspect->is_seeking)
aspect->need_serial_number = true;
aspect->have_working_page = false; /* e-o-s packet ends page */
}
}
}
else {
/* only n bytes of the packet will fit in the buffer */
memcpy(buffer, aspect->working_packet.packet, n);
*bytes += n;
buffer += n;
aspect->working_packet.packet += n;
aspect->working_packet.bytes -= n;
}
}
else {
/* try and get another packet */
const int ret = ogg_stream_packetout(&aspect->stream_state, &aspect->working_packet);
if (ret > 0) {
aspect->have_working_packet = true;
/* if it is the first header packet, check for magic and a supported Ogg FLAC mapping version */
if (aspect->working_packet.bytes > 0 && aspect->working_packet.packet[0] == FLAC__OGG_MAPPING_FIRST_HEADER_PACKET_TYPE) {
const FLAC__byte *b = aspect->working_packet.packet;
if (aspect->working_packet.bytes < (long)header_length)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_NOT_FLAC;
b += FLAC__OGG_MAPPING_PACKET_TYPE_LENGTH;
if (memcmp(b, FLAC__OGG_MAPPING_MAGIC, FLAC__OGG_MAPPING_MAGIC_LENGTH))
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_NOT_FLAC;
b += FLAC__OGG_MAPPING_MAGIC_LENGTH;
aspect->version_major = (uint32_t)(*b);
b += FLAC__OGG_MAPPING_VERSION_MAJOR_LENGTH;
aspect->version_minor = (uint32_t)(*b);
if (aspect->version_major != 1)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_UNSUPPORTED_MAPPING_VERSION;
aspect->working_packet.packet += header_length;
aspect->working_packet.bytes -= header_length;
}
}
else if (ret == 0) {
aspect->have_working_page = false;
}
else { /* ret < 0 */
/* lost sync, we'll leave the working page for the next call */
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
}
}
}
else {
/* try and get another page */
const int ret = ogg_sync_pageout(&aspect->sync_state, &aspect->working_page);
if (ret > 0) {
FLAC__OggDecoderAspectReadStatus status = process_page_(aspect, tell_callback, decoder, client_data);
if(status != FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK)
return status;
}
else if (ret == 0) {
/* need more data */
FLAC__OggDecoderAspectReadStatus status = read_more_data_(aspect, read_callback, bytes_requested - *bytes, decoder, client_data);
if(status != FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK)
return status;
}
else { /* ret < 0 */
/* lost sync, bail out */
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
}
}
}
if (aspect->end_of_stream && *bytes == 0) {
aspect->linkdetails[aspect->current_linknumber].is_last = true;
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
}
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
}
FLAC__OggDecoderAspectReadStatus FLAC__ogg_decoder_aspect_skip_link(FLAC__OggDecoderAspect *aspect, FLAC__OggDecoderAspectReadCallbackProxy read_callback, FLAC__StreamDecoderSeekCallback seek_callback, FLAC__StreamDecoderTellCallback tell_callback, FLAC__StreamDecoderLengthCallback length_callback, const FLAC__StreamDecoder *decoder, void *client_data)
{
if(seek_callback == NULL || tell_callback == NULL || length_callback == NULL)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
/* This extra check is here, because allocation failures while reading cannot always be
* properly passed down the chain with the current API. So, instead, check again */
if(!check_size_of_link_allocation_(aspect))
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
if(aspect->current_linknumber < aspect->number_of_links_indexed) {
if(aspect->linkdetails[aspect->current_linknumber].is_last) {
/* Seek to end of stream */
FLAC__StreamDecoderLengthStatus lstatus;
FLAC__StreamDecoderSeekStatus sstatus;
uint64_t stream_length = 0;
lstatus = length_callback(decoder, &stream_length, client_data);
if(lstatus == FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
if(lstatus == FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
sstatus = seek_callback(decoder, stream_length, client_data);
if(sstatus == FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
if(sstatus == FLAC__STREAM_DECODER_SEEK_STATUS_ERROR)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
}
else {
FLAC__StreamDecoderSeekStatus status;
status = seek_callback(decoder, aspect->linkdetails[aspect->current_linknumber].end_byte,client_data);
if(status == FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
if(status == FLAC__STREAM_DECODER_SEEK_STATUS_ERROR)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
FLAC__ogg_decoder_aspect_flush(aspect);
aspect->beginning_of_link = true;
aspect->need_serial_number = true;
aspect->bos_flag_seen = false;
aspect->current_linknumber++;
aspect->current_linknumber_advance_read = aspect->current_linknumber;
if(!check_size_of_link_allocation_(aspect))
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
aspect->have_working_page = false;
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
}
}
else
{
/* End of current link is unknown, go search for it */
const uint32_t max_page_size = 65307;
uint64_t stream_length = 0;
uint64_t current_pos = 0;
uint64_t page_pos = 0;
uint64_t target_pos = 0;
uint64_t left_pos = 0;
uint64_t right_pos = 0;
FLAC__bool did_a_seek;
FLAC__bool seek_to_left_pos = false;
FLAC__bool keep_reading = false;
FLAC__bool find_bos_twice = aspect->need_serial_number;
int ret = 0;
{
FLAC__StreamDecoderLengthStatus lstatus;
FLAC__StreamDecoderTellStatus tstatus;
lstatus = length_callback(decoder, &stream_length, client_data);
if(lstatus == FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
if(lstatus == FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
tstatus = tell_callback(decoder, ¤t_pos, client_data);
if(tstatus == FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
if(tstatus == FLAC__STREAM_DECODER_TELL_STATUS_ERROR)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
}
current_pos = current_pos - aspect->sync_state.fill + aspect->sync_state.returned;
left_pos = current_pos;
right_pos = stream_length;
while(1){
FLAC__bool seek_was_to_current_link = true;
if(right_pos <= left_pos || right_pos - left_pos < 9) {
/* FLAC frame is at least 9 byte in size */
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
}
target_pos = left_pos + (right_pos - left_pos)/2;
if(keep_reading) {
/* To not end up in a loop, we need to keep reading until are able to change left_pos */
did_a_seek = false;
}
else if(current_pos < target_pos && current_pos + aspect->sync_state.fill - aspect->sync_state.returned > target_pos) {
/* Target location is already in buffer, keep reading */
did_a_seek = false;
}
else if(current_pos < target_pos && current_pos + max_page_size > target_pos) {
/* Target is very close to current location, just reading is probably faster than seeking */
did_a_seek = false;
}
else if(aspect->beginning_of_link) {
/* Still need to save all serial numbers at start of stream, so don't seek yet */
did_a_seek = false;
}
else {
if(seek_to_left_pos || target_pos - left_pos < max_page_size) {
/* Seek to start of bisect area */
target_pos = left_pos;
keep_reading = true;
seek_to_left_pos = false;
}
/* Seek */
if(seek_callback(decoder, target_pos, client_data) != FLAC__STREAM_DECODER_SEEK_STATUS_OK)
return false;
did_a_seek = true;
FLAC__ASSERT(tell_callback(decoder, ¤t_pos, client_data) == FLAC__STREAM_DECODER_TELL_STATUS_OK);
FLAC__ASSERT(current_pos == target_pos);
current_pos = target_pos;
(void)ogg_stream_reset(&aspect->stream_state);
(void)ogg_sync_reset(&aspect->sync_state);
}
/* Get a page, resynchronize if necessary */
while((ret = ogg_sync_pageseek(&aspect->sync_state, &aspect->working_page)) <= 0 && !aspect->end_of_stream) {
if(ret < 0)
current_pos -= ret;
else {
/* need more data */
FLAC__OggDecoderAspectReadStatus status = read_more_data_(aspect, read_callback, 0, decoder, client_data);
if(status != FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK)
return status;
}
}
page_pos = current_pos;
current_pos += aspect->working_page.header_len + aspect->working_page.body_len;
/* Check whether the page serial number belongs to this link or another link */
if (ret > 0) {
if(!aspect->beginning_of_link) {
/* got a page, check the serial number */
long serial_number = ogg_page_serialno(&aspect->working_page);
uint32_t i;
seek_was_to_current_link = false;
if(serial_number == aspect->linkdetails[aspect->current_linknumber].serial_number) {
/* This page belongs to current link */
seek_was_to_current_link = true;
}
for(i = 0; i < aspect->linkdetails[aspect->current_linknumber].number_of_other_streams; i++) {
if(serial_number == aspect->linkdetails[aspect->current_linknumber].other_serial_numbers[i]) {
/* This page belongs to the current link */
seek_was_to_current_link = true;
}
}
if(ogg_page_serialno(&aspect->working_page) == aspect->linkdetails[aspect->current_linknumber].serial_number && ogg_page_eos(&aspect->working_page)) {
/* Found EOS */
aspect->linkdetails[aspect->current_linknumber].end_byte = current_pos;
aspect->linkdetails[aspect->current_linknumber].samples = ogg_page_granulepos(&aspect->working_page);
aspect->number_of_links_indexed++;
aspect->current_linknumber_advance_read = aspect->current_linknumber + 1;
if(!check_size_of_link_allocation_(aspect))
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
aspect->need_serial_number = true;
/* Now, continue loop to check whether we are at end of stream or another link follows */
FLAC__ogg_decoder_aspect_next_link(aspect);
continue;
}
if(seek_was_to_current_link) {
/* If the seek landed on a page with the serial number of the stream we're interested in, we can be sure
* the EOS page will be later in the stream. If the seek landed on a stream with a different serial number
* however, we cannot be sure. It could be the EOS page of that stream has already been seen. In that case
* something else needs to be done, else we could end up in an endless loop */
if(ogg_page_serialno(&aspect->working_page) == aspect->linkdetails[aspect->current_linknumber].serial_number) {
left_pos = current_pos;
keep_reading = false;
}
else {
seek_to_left_pos = true;
}
}
else if(keep_reading) {
/* We read from the left_pos but found nothing interesting, so we can move left_pos up */
left_pos = current_pos;
}
else if(did_a_seek) {
if(right_pos <= page_pos) {
/* Ended up somewhere we've already been */
seek_to_left_pos = true;
}
else
right_pos = page_pos;
}
else {
/* Read forward but found an unknown serial number */
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
}
}
else { /* aspect->beginning_of_link == true */
if(aspect->end_of_stream) {
if(aspect->current_linknumber == 0)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
aspect->current_linknumber--;
aspect->linkdetails[aspect->current_linknumber].is_last = true;
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
}
else {
/* We can end up here for three reasons:
* 1) We've found the end of the link and are looking whether a new one follows it. If that is the case,
* we need to finish after we found it
* 2) We've just started skipping this link, and need to look for other BOS pages first. If that is the case,
* we need to continue after we found them
* 3) We've don't know anything about the link that needs to be skipped yet, and we need to process the BOS
* pages first, and process the BOS pages of the next link */
FLAC__bool need_to_finish = aspect->need_serial_number && !find_bos_twice;
FLAC__OggDecoderAspectReadStatus status = process_page_(aspect, tell_callback, decoder, client_data);
if(status != FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK)
return status;
if(!aspect->need_serial_number) {
if(need_to_finish) {
/* Found start of next link, we're done */
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
}
find_bos_twice = false;
}
if(!aspect->beginning_of_link) {
/* Done scanning BOS pages, move up left_pos */
left_pos = page_pos;
}
}
}
}
else if(aspect->end_of_stream) {
if(aspect->beginning_of_link && !aspect->bos_flag_seen) {
/* We were looking for the next link, but found end of stream instead */
if(aspect->current_linknumber == 0)
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
aspect->current_linknumber--;
aspect->linkdetails[aspect->current_linknumber].is_last = true;
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
}
else if(did_a_seek) {
/* Seeking to target_pos did no result in finding a page, set right_pos to that value */
right_pos = target_pos;
}
else {
/* We expected to find the EOS page without seeking, but ended up at the end of the stream */
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
}
}
else if(ret == 0) {
/* ogg error */
return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
}
}
}
}