mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-08 12:37:01 +03:00
add vorbis/ogg live metadata (and fix some ogg issues) - release
This commit is contained in:
@@ -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,7 +131,7 @@ 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;
|
||||
@@ -139,23 +139,27 @@ static int get_opus_packet(void) {
|
||||
|
||||
while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) {
|
||||
|
||||
// if sync_pageout (or sync_pageseek) is not called here, sync builds ups
|
||||
// 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);
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
// only return a negative value when true end of streaming is reached
|
||||
if (status > 0) packet = status;
|
||||
/* 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;
|
||||
@@ -163,63 +167,72 @@ static int get_opus_packet(void) {
|
||||
}
|
||||
|
||||
static int read_opus_header(void) {
|
||||
int status = 0;
|
||||
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)) {
|
||||
// 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 opus header packet (size:%u)", u->packet.bytes);
|
||||
status = -100;
|
||||
break;
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -585,6 +704,9 @@ void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, un
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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,7 +138,7 @@ 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;
|
||||
@@ -146,40 +146,8 @@ static int get_ogg_packet(void) {
|
||||
|
||||
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
|
||||
// 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(&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) {
|
||||
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);
|
||||
|
||||
// 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);
|
||||
// no need to free comment, it's fake
|
||||
OV(&gv, info_clear, &v->info);
|
||||
status = -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;
|
||||
}
|
||||
//@FIXME: can we have audio on that page as well?
|
||||
break;
|
||||
case OGG_COMMENT_HEADER: {
|
||||
// don't consume VorbisComment, just skip it
|
||||
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) {
|
||||
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) fetch = false;
|
||||
LOG_INFO("comment skipped succesfully");
|
||||
if (packets == 1) continue;
|
||||
}
|
||||
break;
|
||||
|
||||
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
|
||||
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);
|
||||
done = -1;
|
||||
} else {
|
||||
OV(&gv, block_init, &v->decoder, &v->block);
|
||||
v->opened = true;
|
||||
LOG_INFO("codec up and running");
|
||||
done = 1;
|
||||
}
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user