mirror of
https://github.com/claunia/flac.git
synced 2025-12-16 18:54:26 +00:00
revamp the Ogg decoding logic; much more stable now
This commit is contained in:
@@ -33,10 +33,10 @@
|
||||
#include "FLAC/assert.h"
|
||||
#include "private/ogg_decoder_aspect.h"
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
#define min(x,y) ((x)<(y)?(x):(y))
|
||||
#define max(x,y) ((x)>(y)?(x):(y))
|
||||
|
||||
/***********************************************************************
|
||||
*
|
||||
@@ -46,8 +46,6 @@
|
||||
|
||||
FLAC__bool OggFLAC__ogg_decoder_aspect_init(OggFLAC__OggDecoderAspect *aspect)
|
||||
{
|
||||
aspect->need_serial_number = aspect->use_first_serial_number;
|
||||
|
||||
/* we will determine the serial number later if necessary */
|
||||
if(ogg_stream_init(&aspect->stream_state, aspect->serial_number) != 0)
|
||||
return false;
|
||||
@@ -55,6 +53,11 @@ FLAC__bool OggFLAC__ogg_decoder_aspect_init(OggFLAC__OggDecoderAspect *aspect)
|
||||
if(ogg_sync_init(&aspect->sync_state) != 0)
|
||||
return false;
|
||||
|
||||
aspect->need_serial_number = aspect->use_first_serial_number;
|
||||
|
||||
aspect->end_of_stream = false;
|
||||
aspect->have_working_page = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -84,61 +87,132 @@ void OggFLAC__ogg_decoder_aspect_reset(OggFLAC__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;
|
||||
}
|
||||
|
||||
OggFLAC__OggDecoderAspectReadStatus OggFLAC__ogg_decoder_aspect_read_callback_wrapper(OggFLAC__OggDecoderAspect *aspect, FLAC__byte buffer[], unsigned *bytes, OggFLAC__OggDecoderAspectReadCallbackProxy read_callback, void *decoder, void *client_data)
|
||||
{
|
||||
static const unsigned OGG_BYTES_CHUNK = 8192;
|
||||
unsigned ogg_bytes_to_read, ogg_bytes_read;
|
||||
ogg_page page;
|
||||
char *oggbuf;
|
||||
const unsigned bytes_requested = *bytes;
|
||||
|
||||
/*
|
||||
* We have to be careful not to read in more than the
|
||||
* FLAC__StreamDecoder says it has room for. We know
|
||||
* that the size of the decoded data must be no more
|
||||
* than the encoded data we will read.
|
||||
* 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 accomodate 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.
|
||||
*/
|
||||
ogg_bytes_to_read = min(*bytes, OGG_BYTES_CHUNK);
|
||||
oggbuf = ogg_sync_buffer(&aspect->sync_state, ogg_bytes_to_read);
|
||||
*bytes = 0;
|
||||
while (*bytes < bytes_requested && !aspect->end_of_stream) {
|
||||
if (aspect->have_working_page) {
|
||||
if (aspect->have_working_packet) {
|
||||
unsigned n = bytes_requested - *bytes;
|
||||
if ((unsigned)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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 OggFLAC__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) {
|
||||
/* got a page, grab the serial number if necessary */
|
||||
if(aspect->need_serial_number) {
|
||||
aspect->stream_state.serialno = aspect->serial_number = ogg_page_serialno(&aspect->working_page);
|
||||
aspect->need_serial_number = false;
|
||||
}
|
||||
if(ogg_stream_pagein(&aspect->stream_state, &aspect->working_page) == 0) {
|
||||
aspect->have_working_page = true;
|
||||
aspect->have_working_packet = false;
|
||||
}
|
||||
/* else do nothing, could be a page from another stream */
|
||||
}
|
||||
else if (ret == 0) {
|
||||
/* need more data */
|
||||
const unsigned ogg_bytes_to_read = max(bytes_requested - *bytes, OGG_BYTES_CHUNK);
|
||||
char *oggbuf = ogg_sync_buffer(&aspect->sync_state, ogg_bytes_to_read);
|
||||
|
||||
if(0 == oggbuf)
|
||||
return OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
|
||||
if(0 == oggbuf) {
|
||||
return OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
|
||||
}
|
||||
else {
|
||||
unsigned ogg_bytes_read = ogg_bytes_to_read;
|
||||
|
||||
ogg_bytes_read = ogg_bytes_to_read;
|
||||
switch(read_callback(decoder, (FLAC__byte*)oggbuf, &ogg_bytes_read, client_data)) {
|
||||
case OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_OK:
|
||||
break;
|
||||
case OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM:
|
||||
aspect->end_of_stream = true;
|
||||
break;
|
||||
case OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_ABORT:
|
||||
return OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_ABORT;
|
||||
default:
|
||||
FLAC__ASSERT(0);
|
||||
}
|
||||
|
||||
switch(read_callback(decoder, (FLAC__byte*)oggbuf, &ogg_bytes_read, client_data)) {
|
||||
case FLAC__STREAM_DECODER_READ_STATUS_CONTINUE:
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM:
|
||||
return OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
|
||||
case FLAC__STREAM_DECODER_READ_STATUS_ABORT:
|
||||
return OggFLAC__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 OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { /* ret < 0 */
|
||||
/* lost sync, bail out */
|
||||
return OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ogg_sync_wrote(&aspect->sync_state, ogg_bytes_read) < 0)
|
||||
return OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
|
||||
|
||||
*bytes = 0;
|
||||
while(ogg_sync_pageout(&aspect->sync_state, &page) == 1) {
|
||||
/* grab the serial number if necessary */
|
||||
if(aspect->need_serial_number) {
|
||||
aspect->stream_state.serialno = aspect->serial_number = ogg_page_serialno(&page);
|
||||
aspect->need_serial_number = false;
|
||||
}
|
||||
if(ogg_stream_pagein(&aspect->stream_state, &page) == 0) {
|
||||
ogg_packet packet;
|
||||
|
||||
while(ogg_stream_packetout(&aspect->stream_state, &packet) == 1) {
|
||||
memcpy(buffer, packet.packet, packet.bytes);
|
||||
*bytes += packet.bytes;
|
||||
buffer += packet.bytes;
|
||||
}
|
||||
} else {
|
||||
return OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
|
||||
}
|
||||
if (aspect->end_of_stream && *bytes == 0) {
|
||||
return OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
|
||||
}
|
||||
|
||||
return OggFLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
|
||||
|
||||
Reference in New Issue
Block a user