mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-08 20:47:08 +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
|
#define MAX_OPUS_FRAMES 5760
|
||||||
|
|
||||||
struct opus {
|
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_stream_state state;
|
||||||
ogg_packet packet;
|
ogg_packet packet;
|
||||||
ogg_sync_state sync;
|
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;
|
(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;
|
int status, packet = -1;
|
||||||
|
|
||||||
LOCK_S;
|
LOCK_S;
|
||||||
@@ -139,23 +139,27 @@ static int get_opus_packet(void) {
|
|||||||
|
|
||||||
while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) {
|
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) {
|
while (!(status = OG(&go, sync_pageout, &u->sync, &u->page)) && bytes) {
|
||||||
size_t consumed = min(bytes, 4096);
|
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);
|
memcpy(buffer, streambuf->readp, consumed);
|
||||||
OG(&gu, sync_wrote, &u->sync, consumed);
|
OG(&go, sync_wrote, &u->sync, consumed);
|
||||||
|
|
||||||
_buf_inc_readp(streambuf, consumed);
|
_buf_inc_readp(streambuf, consumed);
|
||||||
bytes -= consumed;
|
bytes -= consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have a new page, put it in
|
// 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 (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
|
/* discard header packets. With no packet, we return a negative value
|
||||||
if (status > 0) packet = status;
|
* 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;
|
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
|
||||||
|
|
||||||
UNLOCK_S;
|
UNLOCK_S;
|
||||||
@@ -163,63 +167,72 @@ static int get_opus_packet(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int read_opus_header(void) {
|
static int read_opus_header(void) {
|
||||||
int status = 0;
|
int done = 0;
|
||||||
bool fetch = true;
|
bool fetch = true;
|
||||||
|
|
||||||
LOCK_S;
|
LOCK_S;
|
||||||
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
||||||
|
|
||||||
while (bytes && !status) {
|
while (bytes && !done) {
|
||||||
// first fetch a page if we need one
|
int status;
|
||||||
if (fetch) {
|
|
||||||
|
// get aligned to a page and ready to bring it in
|
||||||
|
do {
|
||||||
size_t consumed = min(bytes, 4096);
|
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);
|
memcpy(buffer, streambuf->readp, consumed);
|
||||||
OG(&gu, sync_wrote, &u->sync, consumed);
|
OG(&go, sync_wrote, &u->sync, consumed);
|
||||||
|
|
||||||
_buf_inc_readp(streambuf, consumed);
|
_buf_inc_readp(streambuf, consumed);
|
||||||
bytes -= 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) {
|
// nothing has been found and we have no more bytes, come back later
|
||||||
case OGG_SYNC:
|
if (status <= 0) break;
|
||||||
u->status = OGG_ID_HEADER;
|
|
||||||
OG(&gu, stream_reset_serialno, &u->state, OG(&gu, page_serialno, &u->page));
|
// always set stream serialno if we have a new one
|
||||||
fetch = false;
|
if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page));
|
||||||
break;
|
|
||||||
case OGG_ID_HEADER:
|
// bring new page in if we want it (otherwise we're just skipping)
|
||||||
status = OG(&gu, stream_pagein, &u->state, &u->page);
|
if (fetch) OG(&go, stream_pagein, &u->state, &u->page);
|
||||||
if (OG(&gu, stream_packetout, &u->state, &u->packet)) {
|
|
||||||
|
// 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)) {
|
if (u->packet.bytes < 19 || memcmp(u->packet.packet, "OpusHead", 8)) {
|
||||||
LOG_ERROR("wrong opus header packet (size:%u)", u->packet.bytes);
|
LOG_ERROR("wrong header packet (size:%u)", u->packet.bytes);
|
||||||
status = -100;
|
done = -100;
|
||||||
break;
|
} else {
|
||||||
}
|
|
||||||
u->status = OGG_COMMENT_HEADER;
|
u->status = OGG_COMMENT_HEADER;
|
||||||
u->channels = u->packet.packet[9];
|
u->channels = u->packet.packet[9];
|
||||||
u->pre_skip = parse_uint16(u->packet.packet + 10);
|
u->pre_skip = parse_uint16(u->packet.packet + 10);
|
||||||
u->rate = parse_uint32(u->packet.packet + 12);
|
u->rate = parse_uint32(u->packet.packet + 12);
|
||||||
u->gain = parse_int16(u->packet.packet + 16);
|
u->gain = parse_int16(u->packet.packet + 16);
|
||||||
u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status);
|
u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status);
|
||||||
|
fetch = false;
|
||||||
if (!u->decoder || status != OPUS_OK) {
|
if (!u->decoder || status != OPUS_OK) {
|
||||||
LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels);
|
LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
LOG_INFO("codec up and running");
|
||||||
}
|
}
|
||||||
fetch = true;
|
}
|
||||||
break;
|
} else if (u->status == OGG_COMMENT_HEADER) {
|
||||||
case OGG_COMMENT_HEADER:
|
// don't consume VorbisComment which could be a huge packet, just skip it
|
||||||
// skip packets to consume VorbisComment. With opus, header packets align on pages
|
if (!OG(&go, page_packets, &u->page)) continue;
|
||||||
status = OG(&gu, page_packets, &u->page);
|
done = 1;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UNLOCK_S;
|
UNLOCK_S;
|
||||||
return status;
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
static decode_state opus_decompress(void) {
|
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);
|
memcpy(write_buf, u->overbuf, u->overframes * BYTES_PER_FRAME);
|
||||||
n = u->overframes;
|
n = u->overframes;
|
||||||
u->overframes = 0;
|
u->overframes = 0;
|
||||||
} else if ((packet = get_opus_packet()) > 0) {
|
} else if ((packet = get_audio_packet()) > 0) {
|
||||||
if (frames < MAX_OPUS_FRAMES) {
|
if (frames < MAX_OPUS_FRAMES) {
|
||||||
// don't have enough contiguous space, use the overflow buffer
|
// 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);
|
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 */
|
* 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);
|
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;
|
UNLOCK_O_direct;
|
||||||
return DECODE_RUNNING;
|
return DECODE_RUNNING;
|
||||||
}
|
}
|
||||||
@@ -342,7 +355,7 @@ static decode_state opus_decompress(void) {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
LOG_INFO("opus decode error: %d", n);
|
LOG_INFO("decode error: %d", n);
|
||||||
UNLOCK_O_direct;
|
UNLOCK_O_direct;
|
||||||
return DECODE_COMPLETE;
|
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);
|
if (!u->overbuf) u->overbuf = malloc(MAX_OPUS_FRAMES * BYTES_PER_FRAME);
|
||||||
|
|
||||||
u->status = OGG_SYNC;
|
u->status = OGG_ID_HEADER;
|
||||||
u->overframes = 0;
|
u->overframes = 0;
|
||||||
|
|
||||||
OG(&go, stream_clear, &u->state);
|
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);
|
stream_file(header, header_len, strm->threshold * 1024);
|
||||||
autostart -= 2;
|
autostart -= 2;
|
||||||
} else {
|
} 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);
|
sendSTAT("STMc", 0);
|
||||||
sentSTMu = sentSTMo = sentSTMl = false;
|
sentSTMu = sentSTMo = sentSTMl = false;
|
||||||
|
|||||||
@@ -580,12 +580,26 @@ struct streamstate {
|
|||||||
u32_t meta_next;
|
u32_t meta_next;
|
||||||
u32_t meta_left;
|
u32_t meta_left;
|
||||||
bool meta_send;
|
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_init(log_level level, unsigned stream_buf_size);
|
||||||
void stream_close(void);
|
void stream_close(void);
|
||||||
void stream_file(const char *header, size_t header_len, unsigned threshold);
|
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);
|
bool stream_disconnect(void);
|
||||||
|
|
||||||
// decode.c
|
// decode.c
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ static bool running = true;
|
|||||||
static void _disconnect(stream_state state, disconnect_code disconnect) {
|
static void _disconnect(stream_state state, disconnect_code disconnect) {
|
||||||
stream.state = state;
|
stream.state = state;
|
||||||
stream.disconnect = disconnect;
|
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 USE_SSL
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
SSL_shutdown(ssl);
|
SSL_shutdown(ssl);
|
||||||
@@ -160,6 +162,122 @@ static void _disconnect(stream_state state, disconnect_code disconnect) {
|
|||||||
wake_controller();
|
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() {
|
static void *stream_thread() {
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
@@ -343,6 +461,7 @@ static void *stream_thread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
|
stream_ogg(n);
|
||||||
_buf_inc_writep(streambuf, n);
|
_buf_inc_writep(streambuf, n);
|
||||||
stream.bytes += n;
|
stream.bytes += n;
|
||||||
if (stream.meta_interval) {
|
if (stream.meta_interval) {
|
||||||
@@ -485,7 +604,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
|
|||||||
UNLOCK;
|
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;
|
struct sockaddr_in addr;
|
||||||
|
|
||||||
#if EMBEDDED
|
#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.bytes = 0;
|
||||||
stream.threshold = threshold;
|
stream.threshold = threshold;
|
||||||
|
|
||||||
|
stream.ogg.miss = stream.ogg.match = 0;
|
||||||
|
stream.ogg.state = (codec == 'o' || codec == 'p') ? STREAM_OGG_SYNC : STREAM_OGG_OFF;
|
||||||
|
|
||||||
UNLOCK;
|
UNLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,6 +726,8 @@ bool stream_disconnect(void) {
|
|||||||
disc = true;
|
disc = true;
|
||||||
}
|
}
|
||||||
stream.state = STOPPED;
|
stream.state = STOPPED;
|
||||||
|
if (stream.ogg.state == STREAM_OGG_HEADER && stream.ogg.data) free(stream.ogg.data);
|
||||||
|
stream.ogg.data = NULL;
|
||||||
UNLOCK;
|
UNLOCK;
|
||||||
return disc;
|
return disc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ static inline int32_t clip15(int32_t x) {
|
|||||||
|
|
||||||
struct vorbis {
|
struct vorbis {
|
||||||
bool opened;
|
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 {
|
struct {
|
||||||
ogg_stream_state state;
|
ogg_stream_state state;
|
||||||
ogg_packet packet;
|
ogg_packet packet;
|
||||||
@@ -138,7 +138,7 @@ extern struct processstate process;
|
|||||||
#define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__)
|
#define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int get_ogg_packet(void) {
|
static int get_audio_packet(void) {
|
||||||
int status, packet = -1;
|
int status, packet = -1;
|
||||||
|
|
||||||
LOCK_S;
|
LOCK_S;
|
||||||
@@ -146,40 +146,8 @@ static int get_ogg_packet(void) {
|
|||||||
|
|
||||||
while (!(status = OG(&go, stream_packetout, &v->state, &v->packet)) && bytes) {
|
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) {
|
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);
|
size_t consumed = min(bytes, 4096);
|
||||||
char* buffer = OG(&go, sync_buffer, &v->sync, consumed);
|
char* buffer = OG(&go, sync_buffer, &v->sync, consumed);
|
||||||
memcpy(buffer, streambuf->readp, consumed);
|
memcpy(buffer, streambuf->readp, consumed);
|
||||||
@@ -187,81 +155,122 @@ static int read_vorbis_header(void) {
|
|||||||
|
|
||||||
_buf_inc_readp(streambuf, consumed);
|
_buf_inc_readp(streambuf, consumed);
|
||||||
bytes -= consumed;
|
bytes -= consumed;
|
||||||
|
|
||||||
if (!OG(&go, sync_pageseek, &v->sync, &v->page)) continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (v->status) {
|
// if we have a new page, put it in and reset serialno at BoS
|
||||||
case OGG_SYNC:
|
if (status) {
|
||||||
v->status = OGG_ID_HEADER;
|
OG(&go, stream_pagein, &v->state, &v->page);
|
||||||
OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
|
if (OG(&go, page_bos, &v->page)) 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);
|
/* odd packets are not audio and should be discarded. With no packet, we
|
||||||
if (!OG(&go, stream_packetout, &v->state, &v->packet)) break;
|
* 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);
|
OV(&gv, info_init, &v->info);
|
||||||
status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet);
|
status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet);
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
LOG_ERROR("vorbis id header packet error %d", status);
|
LOG_ERROR("id header packet error %d", status);
|
||||||
status = -1;
|
done = -1;
|
||||||
} else {
|
} else {
|
||||||
v->channels = v->info.channels;
|
v->channels = v->info.channels;
|
||||||
v->rate = v->info.rate;
|
v->rate = v->info.rate;
|
||||||
v->status = OGG_COMMENT_HEADER;
|
v->status = OGG_COMMENT_HEADER;
|
||||||
|
fetch = false;
|
||||||
// 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");
|
|
||||||
LOG_INFO("id acquired");
|
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
|
if (v->status == OGG_COMMENT_HEADER) {
|
||||||
status = OG(&go, stream_packetout, &v->state, &v->packet);
|
// don't consume VorbisComment which could be a huge packet, just skip it
|
||||||
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
|
|
||||||
int packets = OG(&go, page_packets, &v->page);
|
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;
|
v->status = OGG_SETUP_HEADER;
|
||||||
OG(&go, stream_pagein, &v->state, &v->page);
|
OG(&go, stream_pagein, &v->state, &v->page);
|
||||||
OG(&go, stream_packetout, &v->state, &v->packet);
|
OG(&go, stream_packetout, &v->state, &v->packet);
|
||||||
|
|
||||||
OV(&gv, comment_init, &v->comment);
|
OV(&gv, comment_init, &v->comment);
|
||||||
v->comment.vendor = "N/A";
|
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
|
// because of lack of page alignment, we might have the setup page already fully in
|
||||||
if (packets > 1) fetch = false;
|
if (packets == 1) continue;
|
||||||
LOG_INFO("comment skipped succesfully");
|
|
||||||
}
|
}
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UNLOCK_S;
|
UNLOCK_S;
|
||||||
return status;
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) {
|
inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) {
|
||||||
@@ -317,12 +326,12 @@ static decode_state vorbis_decode(void) {
|
|||||||
if (v->overflow) {
|
if (v->overflow) {
|
||||||
n = pcm_out(&v->decoder, &pcm);
|
n = pcm_out(&v->decoder, &pcm);
|
||||||
v->overflow = n - min(n, frames);
|
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);
|
n = OV(&gv, synthesis, &v->block, &v->packet);
|
||||||
if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block);
|
if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block);
|
||||||
if (n == 0) n = pcm_out(&v->decoder, &pcm);
|
if (n == 0) n = pcm_out(&v->decoder, &pcm);
|
||||||
v->overflow = n - min(n, frames);
|
v->overflow = n - min(n, frames);
|
||||||
} else if (!packet && !OG(&go, page_eos, &v->page)) {
|
} else if (!packet) {
|
||||||
UNLOCK_O_direct;
|
UNLOCK_O_direct;
|
||||||
return DECODE_RUNNING;
|
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->opened = false;
|
||||||
v->status = OGG_SYNC;
|
v->status = OGG_ID_HEADER;
|
||||||
v->overflow = 0;
|
v->overflow = 0;
|
||||||
|
|
||||||
OG(&go, stream_clear, &v->state);
|
OG(&go, stream_clear, &v->state);
|
||||||
|
|||||||
Reference in New Issue
Block a user