diff --git a/components/squeezelite/opus.c b/components/squeezelite/opus.c index 0086c3f0..0c87d3e8 100644 --- a/components/squeezelite/opus.c +++ b/components/squeezelite/opus.c @@ -44,7 +44,7 @@ #define MAX_OPUS_FRAMES 5760 struct opus { - enum {OGG_SYNC, OGG_ID_HEADER, OGG_COMMENT_HEADER} status; + enum { OGG_ID_HEADER, OGG_COMMENT_HEADER } status; ogg_stream_state state; ogg_packet packet; ogg_sync_state sync; @@ -131,95 +131,108 @@ static opus_uint32 parse_uint32(const unsigned char* _data) { (opus_uint32)_data[2] << 16 | (opus_uint32)_data[3] << 24; } -static int get_opus_packet(void) { +static int get_audio_packet(void) { int status, packet = -1; LOCK_S; size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); - + while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) { - - // if sync_pageout (or sync_pageseek) is not called here, sync builds ups - while (!(status = OG(&go, sync_pageout, &u->sync, &u->page)) && bytes) { + + // if sync_pageout (or sync_pageseek) is not called here, sync buffers build up + while (!(status = OG(&go, sync_pageout, &u->sync, &u->page)) && bytes) { size_t consumed = min(bytes, 4096); - char* buffer = OG(&gu, sync_buffer, &u->sync, consumed); + char* buffer = OG(&go, sync_buffer, &u->sync, consumed); memcpy(buffer, streambuf->readp, consumed); - OG(&gu, sync_wrote, &u->sync, consumed); + OG(&go, sync_wrote, &u->sync, consumed); _buf_inc_readp(streambuf, consumed); bytes -= consumed; - } + } - // if we have a new page, put it in - if (status) OG(&go, stream_pagein, &u->state, &u->page); - } - - // only return a negative value when true end of streaming is reached - if (status > 0) packet = status; - else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0; + // if we have a new page, put it in and reset serialno at BoS + if (status) { + OG(&go, stream_pagein, &u->state, &u->page); + if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page)); + } + } + + /* discard header packets. With no packet, we return a negative value + * when there is really nothing more to proceed */ + if (status > 0 && memcmp(u->packet.packet, "OpusHead", 8) && memcmp(u->packet.packet, "OpusTags", 8)) packet = status; + else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0; UNLOCK_S; return packet; } static int read_opus_header(void) { - int status = 0; - bool fetch = true; + int done = 0; + bool fetch = true; LOCK_S; size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); - while (bytes && !status) { - // first fetch a page if we need one - if (fetch) { + while (bytes && !done) { + int status; + + // get aligned to a page and ready to bring it in + do { size_t consumed = min(bytes, 4096); - char* buffer = OG(&gu, sync_buffer, &u->sync, consumed); + + char* buffer = OG(&go, sync_buffer, &u->sync, consumed); memcpy(buffer, streambuf->readp, consumed); - OG(&gu, sync_wrote, &u->sync, consumed); + OG(&go, sync_wrote, &u->sync, consumed); _buf_inc_readp(streambuf, consumed); bytes -= consumed; - if (!OG(&gu, sync_pageseek, &u->sync, &u->page)) continue; - } + status = fetch ? OG(&go, sync_pageout, &u->sync, &u->page) : + OG(&go, sync_pageseek, &u->sync, &u->page); + } while (bytes && status <= 0); - switch (u->status) { - case OGG_SYNC: - u->status = OGG_ID_HEADER; - OG(&gu, stream_reset_serialno, &u->state, OG(&gu, page_serialno, &u->page)); - fetch = false; - break; - case OGG_ID_HEADER: - status = OG(&gu, stream_pagein, &u->state, &u->page); - if (OG(&gu, stream_packetout, &u->state, &u->packet)) { - if (u->packet.bytes < 19 || memcmp(u->packet.packet, "OpusHead", 8)) { - LOG_ERROR("wrong opus header packet (size:%u)", u->packet.bytes); - status = -100; - break; - } - u->status = OGG_COMMENT_HEADER; + // nothing has been found and we have no more bytes, come back later + if (status <= 0) break; + + // always set stream serialno if we have a new one + if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page)); + + // bring new page in if we want it (otherwise we're just skipping) + if (fetch) OG(&go, stream_pagein, &u->state, &u->page); + + // no need for a switch...case + if (u->status == OGG_ID_HEADER) { + // we need the id packet, get more pages if we don't + if (OG(&go, stream_packetout, &u->state, &u->packet) <= 0) continue; + + // make sure this is a valid packet + if (u->packet.bytes < 19 || memcmp(u->packet.packet, "OpusHead", 8)) { + LOG_ERROR("wrong header packet (size:%u)", u->packet.bytes); + done = -100; + } else { + u->status = OGG_COMMENT_HEADER; u->channels = u->packet.packet[9]; u->pre_skip = parse_uint16(u->packet.packet + 10); u->rate = parse_uint32(u->packet.packet + 12); u->gain = parse_int16(u->packet.packet + 16); u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status); + fetch = false; if (!u->decoder || status != OPUS_OK) { LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels); } + else { + LOG_INFO("codec up and running"); + } } - fetch = true; - break; - case OGG_COMMENT_HEADER: - // skip packets to consume VorbisComment. With opus, header packets align on pages - status = OG(&gu, page_packets, &u->page); - break; - default: - break; + } else if (u->status == OGG_COMMENT_HEADER) { + // don't consume VorbisComment which could be a huge packet, just skip it + if (!OG(&go, page_packets, &u->page)) continue; + done = 1; } } UNLOCK_S; - return status; + return done; } static decode_state opus_decompress(void) { @@ -271,7 +284,7 @@ static decode_state opus_decompress(void) { memcpy(write_buf, u->overbuf, u->overframes * BYTES_PER_FRAME); n = u->overframes; u->overframes = 0; - } else if ((packet = get_opus_packet()) > 0) { + } else if ((packet = get_audio_packet()) > 0) { if (frames < MAX_OPUS_FRAMES) { // don't have enough contiguous space, use the overflow buffer n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) u->overbuf, MAX_OPUS_FRAMES, 0); @@ -286,7 +299,7 @@ static decode_state opus_decompress(void) { * outputbuf and streambuf for maybe a long time while we process it all, so don't do that */ n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) write_buf, frames, 0); } - } else if (!packet && !OG(&go, page_eos, &u->page)) { + } else if (!packet) { UNLOCK_O_direct; return DECODE_RUNNING; } @@ -342,7 +355,7 @@ static decode_state opus_decompress(void) { } else { - LOG_INFO("opus decode error: %d", n); + LOG_INFO("decode error: %d", n); UNLOCK_O_direct; return DECODE_COMPLETE; } @@ -357,7 +370,7 @@ static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { if (!u->overbuf) u->overbuf = malloc(MAX_OPUS_FRAMES * BYTES_PER_FRAME); - u->status = OGG_SYNC; + u->status = OGG_ID_HEADER; u->overframes = 0; OG(&go, stream_clear, &u->state); diff --git a/components/squeezelite/slimproto.c b/components/squeezelite/slimproto.c index 1cfe5fbd..de915d63 100644 --- a/components/squeezelite/slimproto.c +++ b/components/squeezelite/slimproto.c @@ -393,7 +393,7 @@ static void process_strm(u8_t *pkt, int len) { stream_file(header, header_len, strm->threshold * 1024); autostart -= 2; } else { - stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2); + stream_sock(ip, port, strm->format, header, header_len, strm->threshold * 1024, autostart >= 2); } sendSTAT("STMc", 0); sentSTMu = sentSTMo = sentSTMl = false; diff --git a/components/squeezelite/squeezelite.h b/components/squeezelite/squeezelite.h index 5b236669..bbb27d23 100644 --- a/components/squeezelite/squeezelite.h +++ b/components/squeezelite/squeezelite.h @@ -580,12 +580,26 @@ struct streamstate { u32_t meta_next; u32_t meta_left; bool meta_send; + struct { + enum { STREAM_OGG_OFF, STREAM_OGG_SYNC, STREAM_OGG_HEADER, STREAM_OGG_SEGMENTS, STREAM_OGG_PAGE } state; + u32_t want, miss, match; + u8_t* data; +#pragma pack(push, 1) + struct { + char pattern[4]; + u8_t version, type; + u64_t granule; + u32_t serial, page, checksum; + u8_t count; + } header; + } ogg; +#pragma pack(pop) }; void stream_init(log_level level, unsigned stream_buf_size); void stream_close(void); void stream_file(const char *header, size_t header_len, unsigned threshold); -void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait); +void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait); bool stream_disconnect(void); // decode.c diff --git a/components/squeezelite/stream.c b/components/squeezelite/stream.c index 794126ad..5f952bb1 100644 --- a/components/squeezelite/stream.c +++ b/components/squeezelite/stream.c @@ -148,6 +148,8 @@ static bool running = true; static void _disconnect(stream_state state, disconnect_code disconnect) { stream.state = state; stream.disconnect = disconnect; + if (stream.ogg.state == STREAM_OGG_HEADER && stream.ogg.data) free(stream.ogg.data); + stream.ogg.data = NULL; #if USE_SSL if (ssl) { SSL_shutdown(ssl); @@ -160,6 +162,122 @@ static void _disconnect(stream_state state, disconnect_code disconnect) { wake_controller(); } +static u32_t memfind(const u8_t* haystack, u32_t n, const char* needle, u32_t len, u32_t *offset) { + int i; + for (i = 0; i < n && *offset != len; i++) *offset = (haystack[i] == needle[*offset]) ? *offset + 1 : 0; + return i; +} + +static void stream_ogg(size_t n) { + if (stream.ogg.state == STREAM_OGG_OFF) return; + u8_t* p = streambuf->writep; + + while (n) { + size_t consumed = min(stream.ogg.miss, n); + + // copy as many bytes as possible and come back later if we do'nt have enough + if (stream.ogg.data) { + memcpy(stream.ogg.data + stream.ogg.want - stream.ogg.miss, p, consumed); + stream.ogg.miss -= consumed; + if (stream.ogg.miss) return; + } + + // we have what we want, let's parse + switch (stream.ogg.state) { + case STREAM_OGG_SYNC: { + stream.ogg.miss -= consumed; + if (consumed) break; + + // we have to memorize position in case any of last 3 bytes match... + int pos = memfind(p, n, "OggS", 4, &stream.ogg.match); + if (stream.ogg.match == 4) { + consumed = pos - stream.ogg.match; + stream.ogg.state = STREAM_OGG_HEADER; + stream.ogg.miss = stream.ogg.want = sizeof(stream.ogg.header); + stream.ogg.data = (u8_t*) &stream.ogg.header; + stream.ogg.match = 0; + } else { + LOG_INFO("OggS not at expected position"); + return; + } + break; + } + case STREAM_OGG_HEADER: + if (!memcmp(stream.ogg.header.pattern, "OggS", 4)) { + stream.ogg.miss = stream.ogg.want = stream.ogg.header.count; + stream.ogg.data = malloc(stream.ogg.miss); + stream.ogg.state = STREAM_OGG_SEGMENTS; + } else { + stream.ogg.state = STREAM_OGG_SYNC; + stream.ogg.data = NULL; + } + break; + case STREAM_OGG_SEGMENTS: + // calculate size of page using lacing values + for (int i = 0; i < stream.ogg.want; i++) stream.ogg.miss += stream.ogg.data[i]; + stream.ogg.want = stream.ogg.miss; + + if (stream.ogg.header.granule == 0) { + // granule 0 means a new stream, so let's look into it + stream.ogg.state = STREAM_OGG_PAGE; + stream.ogg.data = realloc(stream.ogg.data, stream.ogg.want); + } else { + // otherwise, jump over data + stream.ogg.state = STREAM_OGG_SYNC; + free(stream.ogg.data); + stream.ogg.data = NULL; + } + break; + case STREAM_OGG_PAGE: { + u32_t offset = 0; + + // try to find one of valid Ogg pattern (vorbis, opus) + for (char** tag = (char*[]) { "\x3vorbis", "OpusTags", NULL }; *tag; tag++, offset = 0) { + u32_t pos = memfind(stream.ogg.data, stream.ogg.want, *tag, strlen(*tag), &offset); + if (offset != strlen(*tag)) continue; + + // u32:len,char[]:vendorId, u32:N, N x (u32:len,char[]:comment) + char* p = (char*) stream.ogg.data + pos; + p += *p + 4; + u32_t count = *p; + p += 4; + + // LMS metadata format for Ogg is "Ogg", N x (u16:len,char[]:comment) + memcpy(stream.header, "Ogg", 3); + stream.header_len = 3; + + for (u32_t len; count--; p += len) { + len = *p; + p += 4; + + // only report what we use and don't overflow (network byte order) + if (!strncasecmp(p, "TITLE=", 6) || !strncasecmp(p, "ARTIST=", 7) || !strncasecmp(p, "ALBUM=", 6)) { + if (stream.header_len + len > MAX_HEADER) break; + stream.header[stream.header_len++] = len >> 8; + stream.header[stream.header_len++] = len; + memcpy(stream.header + stream.header_len, p, len); + stream.header_len += len; + } + } + + stream.meta_send = true; + wake_controller(); + LOG_INFO("Ogg meta len: %u", stream.header_len); + break; + } + free(stream.ogg.data); + stream.ogg.state = STREAM_OGG_SYNC; + break; + } + default: + break; + } + + p += consumed; + n -= consumed; + } +} + static void *stream_thread() { while (running) { @@ -343,6 +461,7 @@ static void *stream_thread() { } if (n > 0) { + stream_ogg(n); _buf_inc_writep(streambuf, n); stream.bytes += n; if (stream.meta_interval) { @@ -485,7 +604,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) { UNLOCK; } -void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) { +void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait) { struct sockaddr_in addr; #if EMBEDDED @@ -584,6 +703,9 @@ void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, un stream.sent_headers = false; stream.bytes = 0; stream.threshold = threshold; + + stream.ogg.miss = stream.ogg.match = 0; + stream.ogg.state = (codec == 'o' || codec == 'p') ? STREAM_OGG_SYNC : STREAM_OGG_OFF; UNLOCK; } @@ -604,6 +726,8 @@ bool stream_disconnect(void) { disc = true; } stream.state = STOPPED; + if (stream.ogg.state == STREAM_OGG_HEADER && stream.ogg.data) free(stream.ogg.data); + stream.ogg.data = NULL; UNLOCK; return disc; } diff --git a/components/squeezelite/vorbis.c b/components/squeezelite/vorbis.c index 1a3f6e80..0bb041c4 100644 --- a/components/squeezelite/vorbis.c +++ b/components/squeezelite/vorbis.c @@ -50,7 +50,7 @@ static inline int32_t clip15(int32_t x) { struct vorbis { bool opened; - enum { OGG_SYNC, OGG_ID_HEADER, OGG_COMMENT_HEADER, OGG_SETUP_HEADER } status; + enum { OGG_ID_HEADER, OGG_COMMENT_HEADER, OGG_SETUP_HEADER } status; struct { ogg_stream_state state; ogg_packet packet; @@ -138,48 +138,16 @@ extern struct processstate process; #define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__) #endif -static int get_ogg_packet(void) { +static int get_audio_packet(void) { int status, packet = -1; LOCK_S; size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); while (!(status = OG(&go, stream_packetout, &v->state, &v->packet)) && bytes) { - - // if sync_pageout (or sync_pageseek) is not called first, sync buffers build ups - while (!(status = OG(&go, sync_pageout, &v->sync, &v->page)) && bytes) { - size_t consumed = min(bytes, 4096); - char* buffer = OG(&gv, sync_buffer, &v->sync, consumed); - memcpy(buffer, streambuf->readp, consumed); - OG(&gv, sync_wrote, &v->sync, consumed); - - _buf_inc_readp(streambuf, consumed); - bytes -= consumed; - } - - // if we have a new page, put it in - if (status) OG(&go, stream_pagein, &v->state, &v->page); - } - - // only return a negative value when true end of streaming is reached - if (status > 0) packet = status; - else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0; - - UNLOCK_S; - return packet; -} - -static int read_vorbis_header(void) { - int status = 0; - bool fetch = true; - - LOCK_S; - - size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); - - while (bytes && !status) { - // first fetch a page if we need one - if (fetch) { + + // if sync_pageout (or sync_pageseek) is not called here, sync buffers build up + while (!(status = OG(&go, sync_pageout, &v->sync, &v->page)) && bytes) { size_t consumed = min(bytes, 4096); char* buffer = OG(&go, sync_buffer, &v->sync, consumed); memcpy(buffer, streambuf->readp, consumed); @@ -187,81 +155,122 @@ static int read_vorbis_header(void) { _buf_inc_readp(streambuf, consumed); bytes -= consumed; - - if (!OG(&go, sync_pageseek, &v->sync, &v->page)) continue; } - switch (v->status) { - case OGG_SYNC: - v->status = OGG_ID_HEADER; - OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page)); - fetch = false; - break; - case OGG_ID_HEADER: - status = OG(&go, stream_pagein, &v->state, &v->page); - if (!OG(&go, stream_packetout, &v->state, &v->packet)) break; - + // if we have a new page, put it in and reset serialno at BoS + if (status) { + OG(&go, stream_pagein, &v->state, &v->page); + if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page)); + } + } + + /* odd packets are not audio and should be discarded. With no packet, we + * return a negative value when there is really nothing more to proceed */ + if (status > 0 && (v->packet.packet[0] & 0x01) == 0) packet = status; + else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0; + + UNLOCK_S; + return packet; +} + +static int read_vorbis_header(void) { + int done = 0; + bool fetch = true; + + LOCK_S; + size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); + + while (bytes && !done) { + int status; + + // get aligned to a page and ready to bring it in + do { + size_t consumed = min(bytes, 4096); + + char* buffer = OG(&go, sync_buffer, &v->sync, consumed); + memcpy(buffer, streambuf->readp, consumed); + OG(&go, sync_wrote, &v->sync, consumed); + + _buf_inc_readp(streambuf, consumed); + bytes -= consumed; + + status = fetch ? OG(&go, sync_pageout, &v->sync, &v->page) : + OG(&go, sync_pageseek, &v->sync, &v->page); + } while (bytes && status <= 0); + + // nothing has been found and we have no more bytes, come back later + if (status <= 0) break; + + // always set stream serialno if we have a new one + if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page)); + + // bring new page in if we want it (otherwise we're just skipping) + if (fetch) OG(&go, stream_pagein, &v->state, &v->page); + + // not a switch...case b/c we might have multiple packets in a page in vorbis + if (v->status == OGG_ID_HEADER) { + // we need the id packet, get more pages if we don't + if (!OG(&go, stream_packetout, &v->state, &v->packet)) continue; + OV(&gv, info_init, &v->info); status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet); if (status) { - LOG_ERROR("vorbis id header packet error %d", status); - status = -1; + LOG_ERROR("id header packet error %d", status); + done = -1; } else { v->channels = v->info.channels; v->rate = v->info.rate; v->status = OGG_COMMENT_HEADER; - - // only fetch if no other packet already in (they should not) - fetch = OG(&go, page_packets, &v->page) <= 1; - if (!fetch) LOG_INFO("id packet should terminate page"); + fetch = false; LOG_INFO("id acquired"); + // we should only have one packet, so get next pages + if (OG(&go, page_packets, &v->page) == 1) continue; } - break; - case OGG_SETUP_HEADER: - // header packets don't align with pages on Vorbis (contrary to Opus) - if (fetch) OG(&go, stream_pagein, &v->state, &v->page); + } + + if (v->status == OGG_COMMENT_HEADER) { + // don't consume VorbisComment which could be a huge packet, just skip it + int packets = OG(&go, page_packets, &v->page); + if (!packets) continue; + + // we have a "fake" comment packet that is just has the last page... + v->status = OGG_SETUP_HEADER; + OG(&go, stream_pagein, &v->state, &v->page); + OG(&go, stream_packetout, &v->state, &v->packet); + + OV(&gv, comment_init, &v->comment); + v->comment.vendor = "N/A"; + fetch = true; + LOG_INFO("comment skipped succesfully"); + + // because of lack of page alignment, we might have the setup page already fully in + if (packets == 1) continue; + } + + if (v->status == OGG_SETUP_HEADER) { + // we need the setup packet, get more pages if we don't + if (OG(&go, stream_packetout, &v->state, &v->packet) <= 0) continue; // finally build a codec if we have the packet - status = OG(&go, stream_packetout, &v->state, &v->packet); - if (status && ((status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet)) || - (status = OV(&gv, synthesis_init, &v->decoder, &v->info)))) { - LOG_ERROR("vorbis setup header packet error %d", status); + if (OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet) || + OV(&gv, synthesis_init, &v->decoder, &v->info)) { + LOG_ERROR("setup header packet error %d", status); // no need to free comment, it's fake OV(&gv, info_clear, &v->info); - status = -1; + done = -1; } else { OV(&gv, block_init, &v->decoder, &v->block); v->opened = true; - LOG_INFO("codec up and running (rate: %d, channels:%d)", v->rate, v->channels); - status = 1; + LOG_INFO("codec up and running"); + done = 1; } - //@FIXME: can we have audio on that page as well? - break; - case OGG_COMMENT_HEADER: { - // don't consume VorbisComment, just skip it - int packets = OG(&go, page_packets, &v->page); - if (packets) { - v->status = OGG_SETUP_HEADER; - OG(&go, stream_pagein, &v->state, &v->page); - OG(&go, stream_packetout, &v->state, &v->packet); - - OV(&gv, comment_init, &v->comment); - v->comment.vendor = "N/A"; - - // because of lack of page alignment, we might have the setup page already fully in - if (packets > 1) fetch = false; - LOG_INFO("comment skipped succesfully"); - } - break; - } - default: break; } } UNLOCK_S; - return status; + return done; } inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) { @@ -317,12 +326,12 @@ static decode_state vorbis_decode(void) { if (v->overflow) { n = pcm_out(&v->decoder, &pcm); v->overflow = n - min(n, frames); - } else if ((packet = get_ogg_packet()) > 0) { + } else if ((packet = get_audio_packet()) > 0) { n = OV(&gv, synthesis, &v->block, &v->packet); if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block); if (n == 0) n = pcm_out(&v->decoder, &pcm); v->overflow = n - min(n, frames); - } else if (!packet && !OG(&go, page_eos, &v->page)) { + } else if (!packet) { UNLOCK_O_direct; return DECODE_RUNNING; } @@ -410,7 +419,7 @@ static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { } v->opened = false; - v->status = OGG_SYNC; + v->status = OGG_ID_HEADER; v->overflow = 0; OG(&go, stream_clear, &v->state);