Skip to content

Commit

Permalink
0.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
philippe44 committed Dec 7, 2023
1 parent db30e00 commit f133481
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 42 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
0.7.0
- HTTP header parsing regression (failed Sonos pause)
- cache whole track on disk fro HTTP range-request
- change default mode to chunk-encoding
- specifiy defaults on command line

0.6.2
- remove memory in cspot (only at exit)
- remove memory leak in cspot (only at exit)
- fix ^C crash due to misoerdering of stop functions
- update libraop with hopefully corrected alac encoder

Expand Down
Binary file renamed SpotRaop-0.6.2.zip → SpotRaop-0.7.0.zip
Binary file not shown.
Binary file renamed SpotUPnP-0.6.2.zip → SpotUPnP-0.7.0.zip
Binary file not shown.
4 changes: 2 additions & 2 deletions spotraop/src/spotraop.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ static char usage[] =
" -U <user> Spotify username\n"
" -P <password> Spotify password\n"
" -L set AirPlay player password\n"
" -c <alac[|pcm> audio format send to player\n"
" -r <96|160|320> set Spotify vorbis codec rate\n"
" -c <alac|pcm> audio format send to player (alac)\n"
" -r <96|160|320> set Spotify vorbis codec rate (160)\n"
" -N <format> transform device name using C format (%s=name)\n"
" -x <config file> read config from file (default is ./config.xml)\n"
" -i <config file> discover players, save <config file> and exit\n"
Expand Down
2 changes: 1 addition & 1 deletion spotraop/src/spotraop.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include "cross_util.h"
#include "spotify.h"

#define VERSION "v0.6.2" " (" __DATE__ " @ " __TIME__ ")"
#define VERSION "v0.7.0" " (" __DATE__ " @ " __TIME__ ")"

/*----------------------------------------------------------------------------*/
/* typedefs */
Expand Down
72 changes: 57 additions & 15 deletions spotupnp/src/HTTPstreamer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@
* Ring buffer (always rolls over)
*/

ringBuffer::ringBuffer(size_t size) {
ringBuffer::ringBuffer(size_t size) : cacheBuffer(size) {
buffer = new uint8_t[size];
this->size = size;
this->write_p = this->read_p = buffer;
this->wrap_p = buffer + size;
}
Expand Down Expand Up @@ -80,12 +79,50 @@ void ringBuffer::write(const uint8_t* src, size_t size) {
total += size;
}

/****************************************************************************************
* File buffer
*/

size_t fileBuffer::read(uint8_t* dst, size_t size, size_t min) {
size = std::min(size, total);
if (size < min) return 0;

fseek(file, readOffset, SEEK_SET);
size_t bytes = fread(dst, 1, size, file);
readOffset += bytes;

return bytes;
}

uint8_t* fileBuffer::readInner(size_t& size) {
if (size > this->size) {
delete[] buffer;
buffer = new uint8_t[size];
this->size = size;
}

// caller *must* consume ALL data
size = std::min(size, total);

fseek(file, readOffset, SEEK_SET);
size_t bytes = fread(buffer, 1, size, file);
readOffset += bytes;

return buffer;
}

void fileBuffer::write(const uint8_t* src, size_t size) {
fseek(file, 0, SEEK_END);
fwrite(src, 1, size, file);
total += size;
}

/****************************************************************************************
* Class to stream audio content with HTTP
*/

HTTPstreamer::HTTPstreamer(struct in_addr addr, std::string id, unsigned index, std::string codec,
bool flow, int64_t contentLength,
bool flow, int64_t contentLength, bool useFileCache,
cspot::TrackInfo trackInfo, std::string_view trackUnique, int32_t startOffset,
onHeadersHandler onHeaders, EoSCallback onEoS) :
bell::Task("HTTP streamer", 32 * 1024, 0, 0) {
Expand All @@ -100,6 +137,8 @@ HTTPstreamer::HTTPstreamer(struct in_addr addr, std::string id, unsigned index,
this->icy.interval = 0;
// for flow mode, start with a negative offset so that we can always substract
this->offset = startOffset;
if (useFileCache) this->cache = std::make_unique<fileBuffer>();
else this->cache = std::make_unique<ringBuffer>();

codecSettings settings;

Expand Down Expand Up @@ -181,7 +220,7 @@ void HTTPstreamer::getMetadata(metadata_t* metadata) {

void HTTPstreamer::flush() {
state = OFF;
cache.flush();
cache->flush();
encoder->flush();
icy.trackId.clear();
}
Expand Down Expand Up @@ -210,11 +249,13 @@ bool HTTPstreamer::connect(int sock) {
end = start = data.data() + offset;

// find eol
while (offset++ < data.size() && *end != '\r' && *end != '\n') end++;
for (end = start = data.data() + offset;
offset < data.size() && *end != '\r' && *end != '\n';
end++, offset++);

if (offset < data.size()) {
line = std::string(start, end);
while ((*end == '\r' || *end == '\n') && offset++ < data.size()) end++;
for (; (*end == '\r' || *end == '\n') && offset < data.size(); end++, offset++);
}

return line;
Expand Down Expand Up @@ -298,26 +339,26 @@ bool HTTPstreamer::connect(int sock) {
*/

// handle range-request
if (auto it = headers.find("range"); it != headers.end() && cache.total) {
if (auto it = headers.find("range"); it != headers.end() && cache->total) {
size_t offset = 0;
(void) !sscanf(it->second.c_str(), "bytes=%zu", &offset);
if (offset && offset >= cache.total - cache.used()) {
if (offset && offset >= cache->total - cache->used()) {
status = "206 Partial Content";
// see note above
if (!isSonos) {
response["Content-Range"] = "bytes " + std::to_string(offset) + "-" + std::to_string(cache.used()) + "/*";
response["Content-Range"] = "bytes " + std::to_string(offset) + "-" + std::to_string(cache->used()) + "/*";
useRange = true;
}
cache.setReadPtr(offset);
cache->setReadPtr(offset);
useCache = true;
}
} else if (cache.total) {
} else if (cache->total) {
// client asking to re-open the resource, do that since begining if we can
cache.setReadPtr(0);
cache->setReadPtr(0);
useCache = true;
// see note above
if (isSonos) length = INT64_MAX;
CSPOT_LOG(info, "re-opening HTTP stream at %d", cache.total - cache.used());
CSPOT_LOG(info, "re-opening HTTP stream at %d", cache->total - cache->used());
}

// Chunked is compatible with range, as it it a message property, and not en entity one
Expand All @@ -330,6 +371,7 @@ bool HTTPstreamer::connect(int sock) {
if (length > 0) responseStr << "Content-Length: " + std::to_string(length) + "\r\n";
responseStr << "Server: spot-connect\r\n";
responseStr << "Content-Type: " + encoder->mimeType + "\r\n";
responseStr << "Accept-Ranges: bytes\r\n";
responseStr << "Connection: close\r\n";
responseStr << "\r\n";

Expand Down Expand Up @@ -368,15 +410,15 @@ ssize_t HTTPstreamer::streamBody(int sock, struct timeval& timeout) {

// cache has priority
if (useCache) {
size = cache.read(scratch, sizeof(scratch));
size = cache->read(scratch, sizeof(scratch));
if (!size) useCache = false;
}

// not using cache or empty cache, get fresh data from encoder
if (!size) {
size = encoder->read(scratch, sizeof(scratch), 0, state == DRAINING);
// cache what we have anyways
cache.write(scratch, size);
cache->write(scratch, size);
}

// we really have nothing, let caller decide what's next
Expand Down
52 changes: 46 additions & 6 deletions spotupnp/src/HTTPstreamer.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,37 @@ typedef std::function<HTTPheaders(HTTPheaders)> onHeadersHandler;
typedef std::function<void(HTTPstreamer *self)> EoSCallback;

/****************************************************************************************
* Ring buffer (always rolls over)
* Cache buffer
*/
class ringBuffer {
class cacheBuffer {
private:
uint8_t* buffer;
uint8_t* read_p, * write_p, * wrap_p;

protected:
uint8_t* buffer;
size_t size;

public:
size_t size, total = 0;
size_t total = 0;

cacheBuffer(size_t size) : size(size) { }
virtual ~cacheBuffer(void) { };
virtual size_t used(void) = 0;
virtual size_t read(uint8_t* dst, size_t max, size_t min = 0) = 0;
virtual uint8_t* readInner(size_t& size) = 0;
virtual void setReadPtr(size_t offset) = 0;
virtual void write(const uint8_t* src, size_t size) = 0;
virtual void flush(void) = 0;
};

/****************************************************************************************
* Ring buffer (always rolls over)
*/
class ringBuffer : public cacheBuffer {
private:
uint8_t* read_p, * write_p, * wrap_p;

public:
ringBuffer(size_t size = 5 * 1024 * 1024);
~ringBuffer(void) { delete[] buffer; }
size_t used(void) { return write_p >= read_p ? write_p - read_p : size - (read_p - write_p); }
Expand All @@ -49,6 +70,25 @@ class ringBuffer {
void flush(void) { read_p = write_p = buffer; total = 0; }
};

/****************************************************************************************
* File buffer
*/
class fileBuffer : public cacheBuffer {
private:
FILE* file;
size_t readOffset = 0;

public:
fileBuffer(size_t size = 128 * 1024) : cacheBuffer(size) { file = tmpfile(); buffer = new uint8_t[size]; }
~fileBuffer(void) { fclose(file); delete[] buffer; }
size_t used(void) { return total; }
size_t read(uint8_t* dst, size_t max, size_t min = 0);
uint8_t* readInner(size_t& size);
void setReadPtr(size_t offset) { readOffset = offset; }
void write(const uint8_t* src, size_t size);
void flush(void) { readOffset = total = 0; }
};

/****************************************************************************************
* Class to stream audio content with HTTP
*/
Expand All @@ -63,7 +103,7 @@ class HTTPstreamer : public bell::Task {
HTTPheaders headers;
int64_t contentLength = HTTP_CL_NONE;
std::unique_ptr<baseCodec> encoder;
ringBuffer cache;
std::unique_ptr<cacheBuffer> cache;
size_t useCache;
uint8_t scratch[32768];
bool flow;
Expand Down Expand Up @@ -91,7 +131,7 @@ class HTTPstreamer : public bell::Task {
uint64_t totalIn = 0, totalOut = 0;

HTTPstreamer(struct in_addr addr, std::string id, unsigned index, std::string codec,
bool flow, int64_t contentLength,
bool flow, int64_t contentLength, bool useFileCache,
cspot::TrackInfo track, std::string_view trackUnique, int32_t startOffset,
onHeadersHandler onHeaders, EoSCallback onEoS);
~HTTPstreamer();
Expand Down
2 changes: 2 additions & 0 deletions spotupnp/src/config_upnp.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ void SaveConfig(char *name, void *ref, bool full) {
XMLUpdateNode(doc, common, false, "codec", glMRConfig.Codec);
XMLUpdateNode(doc, common, false, "vorbis_rate", "%d", glMRConfig.VorbisRate);
XMLUpdateNode(doc, common, false, "flow", "%d", glMRConfig.Flow);
XMLUpdateNode(doc, common, false, "use_filecache", "%d", glMRConfig.UseFileCache);
XMLUpdateNode(doc, common, false, "gapless", "%d", glMRConfig.Gapless);
XMLUpdateNode(doc, common, false, "artwork", "%s", glMRConfig.ArtWork);

Expand Down Expand Up @@ -145,6 +146,7 @@ static void LoadConfigItem(tMRConfig *Conf, char *name, char *val) {
if (!strcmp(name, "codec")) strcpy(Conf->Codec, val);
if (!strcmp(name, "vorbis_rate")) Conf->VorbisRate = atoi(val);
if (!strcmp(name, "flow")) Conf->Flow = atoi(val);
if (!strcmp(name, "use_filecache")) Conf->UseFileCache = atoi(val);
if (!strcmp(name, "gapless")) Conf->Gapless = atoi(val);
if (!strcmp(name, "artwork")) strcpy(Conf->ArtWork, val);
if (!strcmp(name, "credentials")) strcpy(Conf->Credentials, val);
Expand Down
14 changes: 7 additions & 7 deletions spotupnp/src/spotify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class CSpotPlayer : public bell::Task {
std::deque<std::shared_ptr<HTTPstreamer>> streamers;
std::shared_ptr<HTTPstreamer> player;

bool flow;
bool flow, useFileCache;
std::deque<uint32_t> flowMarkers;
cspot::TrackInfo flowTrackInfo;

Expand All @@ -123,7 +123,7 @@ class CSpotPlayer : public bell::Task {
inline static std::string username = "", password = "";

CSpotPlayer(char* name, char* id, char *credentials, struct in_addr addr, AudioFormat audio, char* codec, bool flow,
int64_t contentLength, struct shadowPlayer* shadow, pthread_mutex_t* mutex);
int64_t contentLength, bool useFileCache, struct shadowPlayer* shadow, pthread_mutex_t* mutex);
~CSpotPlayer();
void disconnect(bool abort = false);

Expand All @@ -132,11 +132,11 @@ class CSpotPlayer : public bell::Task {
};

CSpotPlayer::CSpotPlayer(char* name, char* id, char *credentials, struct in_addr addr, AudioFormat format, char* codec, bool flow,
int64_t contentLength, struct shadowPlayer* shadow, pthread_mutex_t* mutex) : bell::Task("playerInstance",
int64_t contentLength, bool useFileCache, struct shadowPlayer* shadow, pthread_mutex_t* mutex) : bell::Task("playerInstance",
48 * 1024, 0, 0),
clientConnected(1), codec(codec), id(id), addr(addr), flow(flow),
name(name), credentials(credentials), format(format), shadow(shadow),
playerMutex(mutex) {
playerMutex(mutex), useFileCache(useFileCache) {
this->contentLength = (flow && contentLength == HTTP_CL_REAL) ? HTTP_CL_NONE : contentLength;
}

Expand Down Expand Up @@ -249,7 +249,7 @@ void CSpotPlayer::trackHandler(std::string_view trackUnique) {

// create a new streamer an run it, unless in flow mode
if (streamers.empty() || !flow) {
auto streamer = std::make_shared<HTTPstreamer>(addr, id, index++, codec, flow, contentLength,
auto streamer = std::make_shared<HTTPstreamer>(addr, id, index++, codec, flow, contentLength, useFileCache,
newTrackInfo, trackUnique, streamers.empty() ? -startOffset : 0,
[this](std::map<std::string, std::string> headers) {
return this->onHeaders(headers);
Expand Down Expand Up @@ -684,14 +684,14 @@ void spotClose(void) {
}

struct spotPlayer* spotCreatePlayer(char* name, char *id, char * credentials, struct in_addr addr, int oggRate,
char *codec, bool flow, int64_t contentLength,
char *codec, bool flow, int64_t contentLength, bool useFileCache,
struct shadowPlayer* shadow, pthread_mutex_t *mutex) {
AudioFormat format = AudioFormat_OGG_VORBIS_160;

if (oggRate == 320) format = AudioFormat_OGG_VORBIS_320;
else if (oggRate == 96) format = AudioFormat_OGG_VORBIS_96;

auto player = new CSpotPlayer(name, id, credentials, addr, format, codec, flow, contentLength, shadow, mutex);
auto player = new CSpotPlayer(name, id, credentials, addr, format, codec, flow, contentLength, useFileCache, shadow, mutex);
if (player->startTask()) return (struct spotPlayer*) player;

delete player;
Expand Down
2 changes: 1 addition & 1 deletion spotupnp/src/spotify.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct HTTPheaderList* shadowHeaders(struct shadowPlayer* shadow, struct HTTPhea
void shadowRequest(struct shadowPlayer* shadow, enum spotEvent event, ...);

struct spotPlayer* spotCreatePlayer(char* name, char* id, char *credentials, struct in_addr addr, int audio, char *codec, bool flow,
int64_t contentLength, struct shadowPlayer* shadow, pthread_mutex_t *mutex);
int64_t contentLength, bool useFileCache, struct shadowPlayer* shadow, pthread_mutex_t *mutex);
void spotDeletePlayer(struct spotPlayer *spotPlayer);
bool spotGetMetaForUrl(struct spotPlayer* spotPlayer, const char* url, metadata_t* metadata);
void spotOpen(uint16_t portBase, uint16_t portRange, char* username, char *password);
Expand Down
Loading

0 comments on commit f133481

Please sign in to comment.