add clang-format file

reformat code
This commit is contained in:
badaix 2019-09-24 22:42:36 +02:00
parent b733f646ea
commit b20add3815
105 changed files with 7773 additions and 7723 deletions

20
.clang-format Normal file
View file

@ -0,0 +1,20 @@
---
AccessModifierOffset: '-4'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: 'false'
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakTemplateDeclarations: 'true'
BreakBeforeBraces: Allman
ColumnLimit: '160'
IndentCaseLabels: 'true'
IndentWidth: '4'
Language: Cpp
MaxEmptyLinesToKeep: '3'
PenaltyBreakComment: '100000'
PointerAlignment: Left
Standard: Cpp11
UseTab: Never
...

View file

@ -1,5 +1,5 @@
# This file is part of snapcast # This file is part of snapcast
# Copyright (C) 2014-2018 Johannes Pohl # Copyright (C) 2014-2019 Johannes Pohl
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by

57
client/browseZeroConf/browseAvahi.cpp Executable file → Normal file
View file

@ -18,16 +18,16 @@
***/ ***/
#include "browseAvahi.h" #include "browseAvahi.h"
#include <stdio.h> #include "aixlog.hpp"
#include "common/snapException.h"
#include <assert.h> #include <assert.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
#include <iostream>
#include "common/snapException.h"
#include "aixlog.hpp"
static AvahiSimplePoll *simple_poll = NULL; static AvahiSimplePoll* simple_poll = NULL;
BrowseAvahi::BrowseAvahi() : client_(NULL), sb_(NULL) BrowseAvahi::BrowseAvahi() : client_(NULL), sb_(NULL)
@ -57,19 +57,9 @@ void BrowseAvahi::cleanUp()
} }
void BrowseAvahi::resolve_callback( void BrowseAvahi::resolve_callback(AvahiServiceResolver* r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol,
AvahiServiceResolver *r, AvahiResolverEvent event, const char* name, const char* type, const char* domain, const char* host_name,
AVAHI_GCC_UNUSED AvahiIfIndex interface, const AvahiAddress* address, uint16_t port, AvahiStringList* txt, AvahiLookupResultFlags flags,
AVAHI_GCC_UNUSED AvahiProtocol protocol,
AvahiResolverEvent event,
const char *name,
const char *type,
const char *domain,
const char *host_name,
const AvahiAddress *address,
uint16_t port,
AvahiStringList *txt,
AvahiLookupResultFlags flags,
AVAHI_GCC_UNUSED void* userdata) AVAHI_GCC_UNUSED void* userdata)
{ {
BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata); BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata);
@ -80,7 +70,8 @@ void BrowseAvahi::resolve_callback(
switch (event) switch (event)
{ {
case AVAHI_RESOLVER_FAILURE: case AVAHI_RESOLVER_FAILURE:
LOG(ERROR) << "(Resolver) Failed to resolve service '" << name << "' of type '" << type << "' in domain '" << domain << "': " << avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))) << "\n"; LOG(ERROR) << "(Resolver) Failed to resolve service '" << name << "' of type '" << type << "' in domain '" << domain
<< "': " << avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))) << "\n";
break; break;
case AVAHI_RESOLVER_FOUND: case AVAHI_RESOLVER_FOUND:
@ -94,7 +85,7 @@ void BrowseAvahi::resolve_callback(
browseAvahi->result_.ip = a; browseAvahi->result_.ip = a;
browseAvahi->result_.port = port; browseAvahi->result_.port = port;
// protocol seems to be unreliable (0 for IPv4 and for IPv6) // protocol seems to be unreliable (0 for IPv4 and for IPv6)
browseAvahi->result_.ip_version = (browseAvahi->result_.ip.find(":") == std::string::npos)?(IPVersion::IPv4):(IPVersion::IPv6); browseAvahi->result_.ip_version = (browseAvahi->result_.ip.find(":") == std::string::npos) ? (IPVersion::IPv4) : (IPVersion::IPv6);
browseAvahi->result_.valid = true; browseAvahi->result_.valid = true;
browseAvahi->result_.iface_idx = interface; browseAvahi->result_.iface_idx = interface;
@ -116,19 +107,11 @@ void BrowseAvahi::resolve_callback(
} }
void BrowseAvahi::browse_callback( void BrowseAvahi::browse_callback(AvahiServiceBrowser* b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char* name,
AvahiServiceBrowser *b, const char* type, const char* domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata)
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiBrowserEvent event,
const char *name,
const char *type,
const char *domain,
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
void* userdata)
{ {
// AvahiClient* client = (AvahiClient*)userdata; // AvahiClient* client = (AvahiClient*)userdata;
BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata); BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata);
assert(b); assert(b);
@ -149,7 +132,8 @@ void BrowseAvahi::browse_callback(
the callback function is called the server will free the callback function is called the server will free
the resolver for us. */ the resolver for us. */
if (!(avahi_service_resolver_new(browseAvahi->client_, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0, resolve_callback, userdata))) if (!(avahi_service_resolver_new(browseAvahi->client_, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0,
resolve_callback, userdata)))
LOG(ERROR) << "Failed to resolve service '" << name << "': " << avahi_strerror(avahi_client_errno(browseAvahi->client_)) << "\n"; LOG(ERROR) << "Failed to resolve service '" << name << "': " << avahi_strerror(avahi_client_errno(browseAvahi->client_)) << "\n";
break; break;
@ -166,12 +150,12 @@ void BrowseAvahi::browse_callback(
} }
void BrowseAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) void BrowseAvahi::client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void* userdata)
{ {
assert(c); assert(c);
/* Called whenever the client or server state changes */ /* Called whenever the client or server state changes */
// BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata); // BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata);
if (state == AVAHI_CLIENT_FAILURE) if (state == AVAHI_CLIENT_FAILURE)
{ {
@ -195,7 +179,8 @@ bool BrowseAvahi::browse(const std::string& serviceName, mDNSResult& result, int
throw SnapException("BrowseAvahi - Failed to create client: " + std::string(avahi_strerror(error))); throw SnapException("BrowseAvahi - Failed to create client: " + std::string(avahi_strerror(error)));
/* Create the service browser */ /* Create the service browser */
if (!(sb_ = avahi_service_browser_new(client_, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, serviceName.c_str(), NULL, (AvahiLookupFlags)0, browse_callback, this))) if (!(sb_ =
avahi_service_browser_new(client_, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, serviceName.c_str(), NULL, (AvahiLookupFlags)0, browse_callback, this)))
throw SnapException("BrowseAvahi - Failed to create service browser: " + std::string(avahi_strerror(avahi_client_errno(client_)))); throw SnapException("BrowseAvahi - Failed to create service browser: " + std::string(avahi_strerror(avahi_client_errno(client_))));
result_.valid = false; result_.valid = false;
@ -220,5 +205,3 @@ bool BrowseAvahi::browse(const std::string& serviceName, mDNSResult& result, int
throw; throw;
} }
} }

16
client/browseZeroConf/browseAvahi.h Executable file → Normal file
View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -21,9 +21,9 @@
#include <avahi-client/client.h> #include <avahi-client/client.h>
#include <avahi-client/lookup.h> #include <avahi-client/lookup.h>
#include <avahi-common/simple-watch.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h> #include <avahi-common/error.h>
#include <avahi-common/malloc.h>
#include <avahi-common/simple-watch.h>
class BrowseAvahi; class BrowseAvahi;
@ -38,9 +38,13 @@ public:
private: private:
void cleanUp(); void cleanUp();
static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, AVAHI_GCC_UNUSED void* userdata); static void resolve_callback(AvahiServiceResolver* r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol,
static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata); AvahiResolverEvent event, const char* name, const char* type, const char* domain, const char* host_name,
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata); const AvahiAddress* address, uint16_t port, AvahiStringList* txt, AvahiLookupResultFlags flags,
AVAHI_GCC_UNUSED void* userdata);
static void browse_callback(AvahiServiceBrowser* b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char* name,
const char* type, const char* domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata);
static void client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void* userdata);
AvahiClient* client_; AvahiClient* client_;
mDNSResult result_; mDNSResult result_;
AvahiServiceBrowser* sb_; AvahiServiceBrowser* sb_;

73
client/browseZeroConf/browseBonjour.cpp Executable file → Normal file
View file

@ -1,14 +1,14 @@
#include "browseBonjour.h" #include "browseBonjour.h"
#include <memory>
#include <iostream>
#include <deque> #include <deque>
#include <iostream>
#include <memory>
#ifdef WINDOWS #ifdef WINDOWS
#include <WinSock2.h> #include <WinSock2.h>
#include <Ws2tcpip.h> #include <Ws2tcpip.h>
#else #else
#include <sys/socket.h>
#include <netdb.h> #include <netdb.h>
#include <sys/socket.h>
#endif #endif
#include "aixlog.hpp" #include "aixlog.hpp"
@ -18,7 +18,7 @@ using namespace std;
struct DNSServiceRefDeleter struct DNSServiceRefDeleter
{ {
void operator () (DNSServiceRef* ref) void operator()(DNSServiceRef* ref)
{ {
DNSServiceRefDeallocate(*ref); DNSServiceRefDeallocate(*ref);
delete ref; delete ref;
@ -141,7 +141,9 @@ struct mDNSResolve
uint16_t port; uint16_t port;
}; };
#define CHECKED(err) if((err)!=kDNSServiceErr_NoError)throw SnapException(BonjourGetError(err) + ":" + to_string(__LINE__)); #define CHECKED(err) \
if ((err) != kDNSServiceErr_NoError) \
throw SnapException(BonjourGetError(err) + ":" + to_string(__LINE__));
void runService(const DNSServiceHandle& service) void runService(const DNSServiceHandle& service)
{ {
@ -173,20 +175,14 @@ bool BrowseBonjour::browse(const string& serviceName, mDNSResult& result, int ti
{ {
DNSServiceHandle service(new DNSServiceRef(NULL)); DNSServiceHandle service(new DNSServiceRef(NULL));
CHECKED(DNSServiceBrowse(service.get(), 0, 0, serviceName.c_str(), "local.", CHECKED(DNSServiceBrowse(service.get(), 0, 0, serviceName.c_str(), "local.",
[](DNSServiceRef service, [](DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
DNSServiceFlags flags, const char* serviceName, const char* regtype, const char* replyDomain, void* context) {
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char* serviceName,
const char* regtype,
const char* replyDomain,
void* context)
{
auto replyCollection = static_cast<deque<mDNSReply>*>(context); auto replyCollection = static_cast<deque<mDNSReply>*>(context);
CHECKED(errorCode); CHECKED(errorCode);
replyCollection->push_back(mDNSReply{ string(serviceName), string(regtype), string(replyDomain) }); replyCollection->push_back(mDNSReply{string(serviceName), string(regtype), string(replyDomain)});
}, &replyCollection)); },
&replyCollection));
runService(service); runService(service);
} }
@ -196,29 +192,22 @@ bool BrowseBonjour::browse(const string& serviceName, mDNSResult& result, int ti
{ {
DNSServiceHandle service(new DNSServiceRef(NULL)); DNSServiceHandle service(new DNSServiceRef(NULL));
for (auto& reply : replyCollection) for (auto& reply : replyCollection)
CHECKED(DNSServiceResolve(service.get(), 0, 0, reply.name.c_str(), reply.regtype.c_str(), reply.domain.c_str(), CHECKED(
[](DNSServiceRef service, DNSServiceResolve(service.get(), 0, 0, reply.name.c_str(), reply.regtype.c_str(), reply.domain.c_str(),
DNSServiceFlags flags, [](DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char* fullName,
uint32_t interfaceIndex, const char* hosttarget, uint16_t port, uint16_t txtLen, const unsigned char* txtRecord, void* context) {
DNSServiceErrorType errorCode,
const char* fullName,
const char* hosttarget,
uint16_t port,
uint16_t txtLen,
const unsigned char* txtRecord,
void* context)
{
auto resultCollection = static_cast<deque<mDNSResolve>*>(context); auto resultCollection = static_cast<deque<mDNSResolve>*>(context);
CHECKED(errorCode); CHECKED(errorCode);
resultCollection->push_back(mDNSResolve { string(hosttarget), ntohs(port) }); resultCollection->push_back(mDNSResolve{string(hosttarget), ntohs(port)});
}, &resolveCollection)); },
&resolveCollection));
runService(service); runService(service);
} }
// DNS/mDNS Resolve // DNS/mDNS Resolve
deque<mDNSResult> resultCollection(resolveCollection.size(), mDNSResult { IPVersion::IPv4, 0, "", "", 0, false }); deque<mDNSResult> resultCollection(resolveCollection.size(), mDNSResult{IPVersion::IPv4, 0, "", "", 0, false});
{ {
DNSServiceHandle service(new DNSServiceRef(NULL)); DNSServiceHandle service(new DNSServiceRef(NULL));
unsigned i = 0; unsigned i = 0;
@ -226,32 +215,24 @@ bool BrowseBonjour::browse(const string& serviceName, mDNSResult& result, int ti
{ {
resultCollection[i].port = resolve.port; resultCollection[i].port = resolve.port;
CHECKED(DNSServiceGetAddrInfo(service.get(), kDNSServiceFlagsLongLivedQuery, 0, kDNSServiceProtocol_IPv4, resolve.fullName.c_str(), CHECKED(DNSServiceGetAddrInfo(service.get(), kDNSServiceFlagsLongLivedQuery, 0, kDNSServiceProtocol_IPv4, resolve.fullName.c_str(),
[](DNSServiceRef service, [](DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
DNSServiceFlags flags, const char* hostname, const sockaddr* address, uint32_t ttl, void* context) {
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char* hostname,
const sockaddr* address,
uint32_t ttl,
void* context)
{
auto result = static_cast<mDNSResult*>(context); auto result = static_cast<mDNSResult*>(context);
result->host = string(hostname); result->host = string(hostname);
result->ip_version = (address->sa_family == AF_INET)?(IPVersion::IPv4):(IPVersion::IPv6); result->ip_version = (address->sa_family == AF_INET) ? (IPVersion::IPv4) : (IPVersion::IPv6);
result->iface_idx = static_cast<int>(interfaceIndex); result->iface_idx = static_cast<int>(interfaceIndex);
char hostIP[NI_MAXHOST]; char hostIP[NI_MAXHOST];
char hostService[NI_MAXSERV]; char hostService[NI_MAXSERV];
if (getnameinfo(address, sizeof(*address), if (getnameinfo(address, sizeof(*address), hostIP, sizeof(hostIP), hostService, sizeof(hostService),
hostIP, sizeof(hostIP), NI_NUMERICHOST | NI_NUMERICSERV) == 0)
hostService, sizeof(hostService),
NI_NUMERICHOST|NI_NUMERICSERV) == 0)
result->ip = string(hostIP); result->ip = string(hostIP);
else else
return; return;
result->valid = true; result->valid = true;
}, &resultCollection[i++])); },
&resultCollection[i++]));
} }
runService(service); runService(service);
} }

0
client/browseZeroConf/browseBonjour.h Executable file → Normal file
View file

0
client/browseZeroConf/browsemDNS.h Executable file → Normal file
View file

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,19 +16,21 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include "clientConnection.h"
#include "aixlog.hpp"
#include "common/snapException.h"
#include "common/strCompat.h"
#include "message/hello.h"
#include <iostream> #include <iostream>
#include <mutex> #include <mutex>
#include "clientConnection.h"
#include "common/strCompat.h"
#include "common/snapException.h"
#include "message/hello.h"
#include "aixlog.hpp"
using namespace std; using namespace std;
ClientConnection::ClientConnection(MessageReceiver* receiver, const std::string& host, size_t port) : socket_(nullptr), active_(false), connected_(false), messageReceiver_(receiver), reqId_(1), host_(host), port_(port), readerThread_(NULL), sumTimeout_(chronos::msec(0)) ClientConnection::ClientConnection(MessageReceiver* receiver, const std::string& host, size_t port)
: socket_(nullptr), active_(false), connected_(false), messageReceiver_(receiver), reqId_(1), host_(host), port_(port), readerThread_(NULL),
sumTimeout_(chronos::msec(0))
{ {
} }
@ -47,10 +49,9 @@ void ClientConnection::socketRead(void* _to, size_t _bytes)
do do
{ {
len += socket_->read_some(asio::buffer((char*)_to + len, toRead)); len += socket_->read_some(asio::buffer((char*)_to + len, toRead));
//cout << "len: " << len << ", error: " << error << endl; // cout << "len: " << len << ", error: " << error << endl;
toRead = _bytes - len; toRead = _bytes - len;
} } while (toRead > 0);
while (toRead > 0);
} }
@ -74,12 +75,12 @@ void ClientConnection::start()
auto iterator = resolver.resolve(query); auto iterator = resolver.resolve(query);
LOG(DEBUG) << "Connecting\n"; LOG(DEBUG) << "Connecting\n";
socket_.reset(new tcp::socket(io_service_)); socket_.reset(new tcp::socket(io_service_));
// struct timeval tv; // struct timeval tv;
// tv.tv_sec = 5; // tv.tv_sec = 5;
// tv.tv_usec = 0; // tv.tv_usec = 0;
// cout << "socket: " << socket->native_handle() << "\n"; // cout << "socket: " << socket->native_handle() << "\n";
// setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); // setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
socket_->connect(*iterator); socket_->connect(*iterator);
connected_ = true; connected_ = true;
SLOG(NOTICE) << "Connected to " << socket_->remote_endpoint().address().to_string() << endl; SLOG(NOTICE) << "Connected to " << socket_->remote_endpoint().address().to_string() << endl;
@ -99,9 +100,11 @@ void ClientConnection::stop()
if (socket_) if (socket_)
{ {
socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec);
if (ec) LOG(ERROR) << "Error in socket shutdown: " << ec.message() << endl; if (ec)
LOG(ERROR) << "Error in socket shutdown: " << ec.message() << endl;
socket_->close(ec); socket_->close(ec);
if (ec) LOG(ERROR) << "Error in socket close: " << ec.message() << endl; if (ec)
LOG(ERROR) << "Error in socket close: " << ec.message() << endl;
} }
if (readerThread_) if (readerThread_)
{ {
@ -110,7 +113,7 @@ void ClientConnection::stop()
delete readerThread_; delete readerThread_;
} }
} }
catch(...) catch (...)
{ {
} }
readerThread_ = NULL; readerThread_ = NULL;
@ -121,12 +124,12 @@ void ClientConnection::stop()
bool ClientConnection::send(const msg::BaseMessage* message) const bool ClientConnection::send(const msg::BaseMessage* message) const
{ {
// std::unique_lock<std::mutex> mlock(mutex_); // std::unique_lock<std::mutex> mlock(mutex_);
//LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n"; // LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n";
std::lock_guard<std::mutex> socketLock(socketMutex_); std::lock_guard<std::mutex> socketLock(socketMutex_);
if (!connected()) if (!connected())
return false; return false;
//LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n"; // LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n";
asio::streambuf streambuf; asio::streambuf streambuf;
std::ostream stream(&streambuf); std::ostream stream(&streambuf);
tv t; tv t;
@ -143,7 +146,7 @@ shared_ptr<msg::SerializedMessage> ClientConnection::sendRequest(const msg::Base
if (++reqId_ >= 10000) if (++reqId_ >= 10000)
reqId_ = 1; reqId_ = 1;
message->id = reqId_; message->id = reqId_;
// LOG(INFO) << "Req: " << message->id << "\n"; // LOG(INFO) << "Req: " << message->id << "\n";
shared_ptr<PendingRequest> pendingRequest(new PendingRequest(reqId_)); shared_ptr<PendingRequest> pendingRequest(new PendingRequest(reqId_));
std::unique_lock<std::mutex> lock(pendingRequestsMutex_); std::unique_lock<std::mutex> lock(pendingRequestsMutex_);
@ -153,7 +156,7 @@ shared_ptr<msg::SerializedMessage> ClientConnection::sendRequest(const msg::Base
{ {
response = pendingRequest->response; response = pendingRequest->response;
sumTimeout_ = chronos::msec(0); sumTimeout_ = chronos::msec(0);
// LOG(INFO) << "Resp: " << pendingRequest->id << "\n"; // LOG(INFO) << "Resp: " << pendingRequest->id << "\n";
} }
else else
{ {
@ -174,21 +177,23 @@ void ClientConnection::getNextMessage()
vector<char> buffer(baseMsgSize); vector<char> buffer(baseMsgSize);
socketRead(&buffer[0], baseMsgSize); socketRead(&buffer[0], baseMsgSize);
baseMessage.deserialize(&buffer[0]); baseMessage.deserialize(&buffer[0]);
// LOG(DEBUG) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << "\n"; // LOG(DEBUG) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " <<
//baseMessage.refersTo << "\n";
if (baseMessage.size > buffer.size()) if (baseMessage.size > buffer.size())
buffer.resize(baseMessage.size); buffer.resize(baseMessage.size);
// { // {
// std::lock_guard<std::mutex> socketLock(socketMutex_); // std::lock_guard<std::mutex> socketLock(socketMutex_);
socketRead(&buffer[0], baseMessage.size); socketRead(&buffer[0], baseMessage.size);
// } // }
tv t; tv t;
baseMessage.received = t; baseMessage.received = t;
{ {
std::unique_lock<std::mutex> lock(pendingRequestsMutex_); std::unique_lock<std::mutex> lock(pendingRequestsMutex_);
// LOG(DEBUG) << "got lock - getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << "\n"; // LOG(DEBUG) << "got lock - getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ",
//refers: " << baseMessage.refersTo << "\n";
{ {
for (auto req: pendingRequests_) for (auto req : pendingRequests_)
{ {
if (req->id == baseMessage.refersTo) if (req->id == baseMessage.refersTo)
{ {
@ -214,7 +219,7 @@ void ClientConnection::reader()
{ {
try try
{ {
while(active_) while (active_)
{ {
getNextMessage(); getNextMessage();
} }
@ -230,7 +235,3 @@ void ClientConnection::reader()
connected_ = false; connected_ = false;
active_ = false; active_ = false;
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,16 +19,16 @@
#ifndef CLIENT_CONNECTION_H #ifndef CLIENT_CONNECTION_H
#define CLIENT_CONNECTION_H #define CLIENT_CONNECTION_H
#include "common/timeDefs.h"
#include "message/message.h"
#include <asio.hpp>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <set>
#include <string> #include <string>
#include <thread> #include <thread>
#include <atomic>
#include <mutex>
#include <memory>
#include <asio.hpp>
#include <condition_variable>
#include <set>
#include "message/message.h"
#include "common/timeDefs.h"
using asio::ip::tcp; using asio::ip::tcp;
@ -40,7 +40,7 @@ class ClientConnection;
/// Used to synchronize server requests (wait for server response) /// Used to synchronize server requests (wait for server response)
struct PendingRequest struct PendingRequest
{ {
PendingRequest(uint16_t reqId) : id(reqId), response(NULL) {}; PendingRequest(uint16_t reqId) : id(reqId), response(NULL){};
uint16_t id; uint16_t id;
std::shared_ptr<msg::SerializedMessage> response; std::shared_ptr<msg::SerializedMessage> response;
@ -103,7 +103,7 @@ public:
virtual bool connected() const virtual bool connected() const
{ {
return (socket_ != nullptr); return (socket_ != nullptr);
// return (connected_ && socket); // return (connected_ && socket);
} }
protected: protected:
@ -130,7 +130,3 @@ protected:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,37 +16,29 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <iostream>
#include <string>
#include <memory>
#include "controller.h" #include "controller.h"
#include "decoder/pcmDecoder.h" #include "decoder/pcmDecoder.h"
#include <iostream>
#include <memory>
#include <string>
#if defined(HAS_OGG) && (defined(HAS_TREMOR) || defined(HAS_VORBIS)) #if defined(HAS_OGG) && (defined(HAS_TREMOR) || defined(HAS_VORBIS))
#include "decoder/oggDecoder.h" #include "decoder/oggDecoder.h"
#endif #endif
#if defined(HAS_FLAC) #if defined(HAS_FLAC)
#include "decoder/flacDecoder.h" #include "decoder/flacDecoder.h"
#endif #endif
#include "timeProvider.h"
#include "message/time.h"
#include "message/hello.h"
#include "common/snapException.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/snapException.h"
#include "message/hello.h"
#include "message/time.h"
#include "timeProvider.h"
using namespace std; using namespace std;
Controller::Controller(const std::string& hostId, size_t instance, std::shared_ptr<MetadataAdapter> meta) : MessageReceiver(), Controller::Controller(const std::string& hostId, size_t instance, std::shared_ptr<MetadataAdapter> meta)
hostId_(hostId), : MessageReceiver(), hostId_(hostId), instance_(instance), active_(false), latency_(0), stream_(nullptr), decoder_(nullptr), player_(nullptr), meta_(meta),
instance_(instance), serverSettings_(nullptr), async_exception_(nullptr)
active_(false),
latency_(0),
stream_(nullptr),
decoder_(nullptr),
player_(nullptr),
meta_(meta),
serverSettings_(nullptr),
async_exception_(nullptr)
{ {
} }
@ -67,12 +59,13 @@ void Controller::onMessageReceived(ClientConnection* connection, const msg::Base
{ {
msg::PcmChunk* pcmChunk = new msg::PcmChunk(sampleFormat_, 0); msg::PcmChunk* pcmChunk = new msg::PcmChunk(sampleFormat_, 0);
pcmChunk->deserialize(baseMessage, buffer); pcmChunk->deserialize(baseMessage, buffer);
// LOG(DEBUG) << "chunk: " << pcmChunk->payloadSize << ", sampleFormat: " << sampleFormat_.rate << "\n"; // LOG(DEBUG) << "chunk: " << pcmChunk->payloadSize << ", sampleFormat: " << sampleFormat_.rate << "\n";
if (decoder_->decode(pcmChunk)) if (decoder_->decode(pcmChunk))
{ {
//TODO: do decoding in thread? // TODO: do decoding in thread?
stream_->addChunk(pcmChunk); stream_->addChunk(pcmChunk);
//LOG(DEBUG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->getDuration() << ", sec: " << pcmChunk->timestamp.sec << ", usec: " << pcmChunk->timestamp.usec/1000 << ", type: " << pcmChunk->type << "\n"; // LOG(DEBUG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->getDuration() << ", sec: " << pcmChunk->timestamp.sec <<
// ", usec: " << pcmChunk->timestamp.usec/1000 << ", type: " << pcmChunk->type << "\n";
} }
else else
delete pcmChunk; delete pcmChunk;
@ -82,13 +75,14 @@ void Controller::onMessageReceived(ClientConnection* connection, const msg::Base
{ {
msg::Time reply; msg::Time reply;
reply.deserialize(baseMessage, buffer); reply.deserialize(baseMessage, buffer);
TimeProvider::getInstance().setDiff(reply.latency, reply.received - reply.sent);// ToServer(diff / 2); TimeProvider::getInstance().setDiff(reply.latency, reply.received - reply.sent); // ToServer(diff / 2);
} }
else if (baseMessage.type == message_type::kServerSettings) else if (baseMessage.type == message_type::kServerSettings)
{ {
serverSettings_.reset(new msg::ServerSettings()); serverSettings_.reset(new msg::ServerSettings());
serverSettings_->deserialize(baseMessage, buffer); serverSettings_->deserialize(baseMessage, buffer);
LOG(INFO) << "ServerSettings - buffer: " << serverSettings_->getBufferMs() << ", latency: " << serverSettings_->getLatency() << ", volume: " << serverSettings_->getVolume() << ", muted: " << serverSettings_->isMuted() << "\n"; LOG(INFO) << "ServerSettings - buffer: " << serverSettings_->getBufferMs() << ", latency: " << serverSettings_->getLatency()
<< ", volume: " << serverSettings_->getVolume() << ", muted: " << serverSettings_->isMuted() << "\n";
if (stream_ && player_) if (stream_ && player_)
{ {
player_->setVolume(serverSettings_->getVolume() / 100.); player_->setVolume(serverSettings_->getVolume() / 100.);
@ -143,7 +137,7 @@ void Controller::onMessageReceived(ClientConnection* connection, const msg::Base
streamTags_.reset(new msg::StreamTags()); streamTags_.reset(new msg::StreamTags());
streamTags_->deserialize(baseMessage, buffer); streamTags_->deserialize(baseMessage, buffer);
if(meta_) if (meta_)
meta_->push(streamTags_->msg); meta_->push(streamTags_->msg);
} }
@ -205,7 +199,7 @@ void Controller::worker()
/// Do initial time sync with the server /// Do initial time sync with the server
msg::Time timeReq; msg::Time timeReq;
for (size_t n=0; n<50 && active_; ++n) for (size_t n = 0; n < 50 && active_; ++n)
{ {
if (async_exception_) if (async_exception_)
{ {
@ -226,7 +220,7 @@ void Controller::worker()
while (active_) while (active_)
{ {
LOG(DEBUG) << "Main loop\n"; LOG(DEBUG) << "Main loop\n";
for (size_t n=0; n<10 && active_; ++n) for (size_t n = 0; n < 10 && active_; ++n)
{ {
chronos::sleep(100); chronos::sleep(100);
if (async_exception_) if (async_exception_)
@ -248,12 +242,9 @@ void Controller::worker()
player_.reset(); player_.reset();
stream_.reset(); stream_.reset();
decoder_.reset(); decoder_.reset();
for (size_t n=0; (n<10) && active_; ++n) for (size_t n = 0; (n < 10) && active_; ++n)
chronos::sleep(100); chronos::sleep(100);
} }
} }
LOG(DEBUG) << "Thread stopped\n"; LOG(DEBUG) << "Thread stopped\n";
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,13 +19,13 @@
#ifndef CONTROLLER_H #ifndef CONTROLLER_H
#define CONTROLLER_H #define CONTROLLER_H
#include <thread>
#include <atomic>
#include "decoder/decoder.h" #include "decoder/decoder.h"
#include "message/message.h" #include "message/message.h"
#include "message/serverSettings.h" #include "message/serverSettings.h"
#include "message/streamTags.h" #include "message/streamTags.h"
#include "player/pcmDevice.h" #include "player/pcmDevice.h"
#include <atomic>
#include <thread>
#ifdef HAS_ALSA #ifdef HAS_ALSA
#include "player/alsaPlayer.h" #include "player/alsaPlayer.h"
#elif HAS_OPENSL #elif HAS_OPENSL
@ -34,8 +34,8 @@
#include "player/coreAudioPlayer.h" #include "player/coreAudioPlayer.h"
#endif #endif
#include "clientConnection.h" #include "clientConnection.h"
#include "stream.h"
#include "metadata.h" #include "metadata.h"
#include "stream.h"
/// Forwards PCM data to the audio player /// Forwards PCM data to the audio player
@ -86,4 +86,3 @@ private:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -18,17 +18,17 @@
#ifndef DECODER_H #ifndef DECODER_H
#define DECODER_H #define DECODER_H
#include <mutex>
#include "message/pcmChunk.h"
#include "message/codecHeader.h"
#include "common/sampleFormat.h" #include "common/sampleFormat.h"
#include "message/codecHeader.h"
#include "message/pcmChunk.h"
#include <mutex>
class Decoder class Decoder
{ {
public: public:
Decoder() {}; Decoder(){};
virtual ~Decoder() {}; virtual ~Decoder(){};
virtual bool decode(msg::PcmChunk* chunk) = 0; virtual bool decode(msg::PcmChunk* chunk) = 0;
virtual SampleFormat setHeader(msg::CodecHeader* chunk) = 0; virtual SampleFormat setHeader(msg::CodecHeader* chunk) = 0;
@ -38,5 +38,3 @@ protected:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,29 +16,30 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <iostream>
#include <cstring>
#include <cmath>
#include "flacDecoder.h" #include "flacDecoder.h"
#include "common/snapException.h"
#include "common/endian.hpp"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/endian.hpp"
#include "common/snapException.h"
#include <cmath>
#include <cstring>
#include <iostream>
using namespace std; using namespace std;
static FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data); static FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder* decoder, FLAC__byte buffer[], size_t* bytes, void* client_data);
static FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data); static FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder* decoder, const FLAC__Frame* frame, const FLAC__int32* const buffer[],
static void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data); void* client_data);
static void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data); static void metadata_callback(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data);
static void error_callback(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data);
static msg::CodecHeader* flacHeader = NULL; static msg::CodecHeader* flacHeader = NULL;
static msg::PcmChunk* flacChunk = NULL; static msg::PcmChunk* flacChunk = NULL;
static msg::PcmChunk* pcmChunk = NULL; static msg::PcmChunk* pcmChunk = NULL;
static SampleFormat sampleFormat; static SampleFormat sampleFormat;
static FLAC__StreamDecoder *decoder = NULL; static FLAC__StreamDecoder* decoder = NULL;
@ -77,7 +78,7 @@ bool FlacDecoder::decode(msg::PcmChunk* chunk)
if (lastError_) if (lastError_)
{ {
LOG(ERROR) << "FLAC decode error: " << FLAC__StreamDecoderErrorStatusString[*lastError_] << "\n"; LOG(ERROR) << "FLAC decode error: " << FLAC__StreamDecoderErrorStatusString[*lastError_] << "\n";
lastError_= nullptr; lastError_ = nullptr;
return false; return false;
} }
} }
@ -103,7 +104,7 @@ SampleFormat FlacDecoder::setHeader(msg::CodecHeader* chunk)
if ((decoder = FLAC__stream_decoder_new()) == NULL) if ((decoder = FLAC__stream_decoder_new()) == NULL)
throw SnapException("ERROR: allocating decoder"); throw SnapException("ERROR: allocating decoder");
// (void)FLAC__stream_decoder_set_md5_checking(decoder, true); // (void)FLAC__stream_decoder_set_md5_checking(decoder, true);
init_status = FLAC__stream_decoder_init_stream(decoder, read_callback, NULL, NULL, NULL, NULL, write_callback, metadata_callback, error_callback, this); init_status = FLAC__stream_decoder_init_stream(decoder, read_callback, NULL, NULL, NULL, NULL, write_callback, metadata_callback, error_callback, this);
if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
throw SnapException("ERROR: initializing decoder: " + string(FLAC__StreamDecoderInitStatusString[init_status])); throw SnapException("ERROR: initializing decoder: " + string(FLAC__StreamDecoderInitStatusString[init_status]));
@ -117,7 +118,7 @@ SampleFormat FlacDecoder::setHeader(msg::CodecHeader* chunk)
} }
FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder* decoder, FLAC__byte buffer[], size_t* bytes, void* client_data)
{ {
if (flacHeader != NULL) if (flacHeader != NULL)
{ {
@ -127,13 +128,13 @@ FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder,
} }
else if (flacChunk != NULL) else if (flacChunk != NULL)
{ {
// cerr << "read_callback: " << *bytes << ", avail: " << flacChunk->payloadSize << "\n"; // cerr << "read_callback: " << *bytes << ", avail: " << flacChunk->payloadSize << "\n";
static_cast<FlacDecoder*>(client_data)->cacheInfo_.isCachedChunk_ = false; static_cast<FlacDecoder*>(client_data)->cacheInfo_.isCachedChunk_ = false;
if (*bytes > flacChunk->payloadSize) if (*bytes > flacChunk->payloadSize)
*bytes = flacChunk->payloadSize; *bytes = flacChunk->payloadSize;
// if (*bytes == 0) // if (*bytes == 0)
// return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; // return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
memcpy(buffer, flacChunk->payload, *bytes); memcpy(buffer, flacChunk->payload, *bytes);
memmove(flacChunk->payload, flacChunk->payload + *bytes, flacChunk->payloadSize - *bytes); memmove(flacChunk->payload, flacChunk->payload + *bytes, flacChunk->payloadSize - *bytes);
@ -144,7 +145,8 @@ FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder,
} }
FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder* decoder, const FLAC__Frame* frame, const FLAC__int32* const buffer[],
void* client_data)
{ {
(void)decoder; (void)decoder;
@ -170,19 +172,19 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
{ {
int8_t* chunkBuffer = (int8_t*)(pcmChunk->payload + pcmChunk->payloadSize); int8_t* chunkBuffer = (int8_t*)(pcmChunk->payload + pcmChunk->payloadSize);
for (size_t i = 0; i < frame->header.blocksize; i++) for (size_t i = 0; i < frame->header.blocksize; i++)
chunkBuffer[sampleFormat.channels*i + channel] = (int8_t)(buffer[channel][i]); chunkBuffer[sampleFormat.channels * i + channel] = (int8_t)(buffer[channel][i]);
} }
else if (sampleFormat.sampleSize == 2) else if (sampleFormat.sampleSize == 2)
{ {
int16_t* chunkBuffer = (int16_t*)(pcmChunk->payload + pcmChunk->payloadSize); int16_t* chunkBuffer = (int16_t*)(pcmChunk->payload + pcmChunk->payloadSize);
for (size_t i = 0; i < frame->header.blocksize; i++) for (size_t i = 0; i < frame->header.blocksize; i++)
chunkBuffer[sampleFormat.channels*i + channel] = SWAP_16((int16_t)(buffer[channel][i])); chunkBuffer[sampleFormat.channels * i + channel] = SWAP_16((int16_t)(buffer[channel][i]));
} }
else if (sampleFormat.sampleSize == 4) else if (sampleFormat.sampleSize == 4)
{ {
int32_t* chunkBuffer = (int32_t*)(pcmChunk->payload + pcmChunk->payloadSize); int32_t* chunkBuffer = (int32_t*)(pcmChunk->payload + pcmChunk->payloadSize);
for (size_t i = 0; i < frame->header.blocksize; i++) for (size_t i = 0; i < frame->header.blocksize; i++)
chunkBuffer[sampleFormat.channels*i + channel] = SWAP_32((int32_t)(buffer[channel][i])); chunkBuffer[sampleFormat.channels * i + channel] = SWAP_32((int32_t)(buffer[channel][i]));
} }
} }
pcmChunk->payloadSize += bytes; pcmChunk->payloadSize += bytes;
@ -192,22 +194,19 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
} }
void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) void metadata_callback(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data)
{ {
(void)decoder; (void)decoder;
/* print some stats */ /* print some stats */
if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO)
{ {
static_cast<FlacDecoder*>(client_data)->cacheInfo_.sampleRate_ = metadata->data.stream_info.sample_rate; static_cast<FlacDecoder*>(client_data)->cacheInfo_.sampleRate_ = metadata->data.stream_info.sample_rate;
sampleFormat.setFormat( sampleFormat.setFormat(metadata->data.stream_info.sample_rate, metadata->data.stream_info.bits_per_sample, metadata->data.stream_info.channels);
metadata->data.stream_info.sample_rate,
metadata->data.stream_info.bits_per_sample,
metadata->data.stream_info.channels);
} }
} }
void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) void error_callback(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data)
{ {
(void)decoder, (void)client_data; (void)decoder, (void)client_data;
SLOG(ERROR) << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << "\n"; SLOG(ERROR) << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << "\n";
@ -223,8 +222,3 @@ void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderError
// Oct 27 17:47:13 kitchen snapclient[869]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM // Oct 27 17:47:13 kitchen snapclient[869]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM
// Oct 27 17:47:13 kitchen snapclient[869]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC // Oct 27 17:47:13 kitchen snapclient[869]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -60,5 +60,3 @@ public:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,14 +16,14 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <iostream>
#include <cstring>
#include <cmath> #include <cmath>
#include <cstring>
#include <iostream>
#include "oggDecoder.h"
#include "common/snapException.h"
#include "common/endian.hpp"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/endian.hpp"
#include "common/snapException.h"
#include "oggDecoder.h"
using namespace std; using namespace std;
@ -55,7 +55,7 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
stream initial header) We need the first page to get the stream stream initial header) We need the first page to get the stream
serialno. */ serialno. */
int size = chunk->payloadSize; int size = chunk->payloadSize;
char *buffer = ogg_sync_buffer(&oy, size); char* buffer = ogg_sync_buffer(&oy, size);
memcpy(buffer, chunk->payload, size); memcpy(buffer, chunk->payload, size);
ogg_sync_wrote(&oy, size); ogg_sync_wrote(&oy, size);
@ -63,7 +63,7 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
chunk->payloadSize = 0; chunk->payloadSize = 0;
/* The rest is just a straight decode loop until end of stream */ /* The rest is just a straight decode loop until end of stream */
// while(!eos){ // while(!eos){
while(true) while (true)
{ {
int result = ogg_sync_pageout(&oy, &og); int result = ogg_sync_pageout(&oy, &og);
if (result == 0) if (result == 0)
@ -75,8 +75,8 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
continue; continue;
} }
ogg_stream_pagein(&os,&og); /* can safely ignore errors at this point */ ogg_stream_pagein(&os, &og); /* can safely ignore errors at this point */
while(1) while (1)
{ {
result = ogg_stream_packetout(&os, &op); result = ogg_stream_packetout(&os, &op);
@ -87,13 +87,13 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
/* no reason to complain; already complained above */ /* no reason to complain; already complained above */
/* we have a packet. Decode it */ /* we have a packet. Decode it */
#ifdef HAS_TREMOR #ifdef HAS_TREMOR
ogg_int32_t **pcm; ogg_int32_t** pcm;
#else #else
float **pcm; float** pcm;
#endif #endif
int samples; int samples;
if (vorbis_synthesis(&vb,&op) == 0) /* test for success! */ if (vorbis_synthesis(&vb, &op) == 0) /* test for success! */
vorbis_synthesis_blockin(&vd, &vb); vorbis_synthesis_blockin(&vd, &vb);
/* /*
**pcm is a multichannel float vector. In stereo, for **pcm is a multichannel float vector. In stereo, for
@ -111,11 +111,11 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
int8_t* chunkBuffer = (int8_t*)(chunk->payload + chunk->payloadSize); int8_t* chunkBuffer = (int8_t*)(chunk->payload + chunk->payloadSize);
for (int i = 0; i < samples; i++) for (int i = 0; i < samples; i++)
{ {
int8_t& val = chunkBuffer[sampleFormat_.channels*i + channel]; int8_t& val = chunkBuffer[sampleFormat_.channels * i + channel];
#ifdef HAS_TREMOR #ifdef HAS_TREMOR
val = clip<int8_t>(pcm[channel][i], -128, 127); val = clip<int8_t>(pcm[channel][i], -128, 127);
#else #else
val = clip<int8_t>(floor(pcm[channel][i]*127.f + .5f), -128, 127); val = clip<int8_t>(floor(pcm[channel][i] * 127.f + .5f), -128, 127);
#endif #endif
} }
} }
@ -124,11 +124,11 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
int16_t* chunkBuffer = (int16_t*)(chunk->payload + chunk->payloadSize); int16_t* chunkBuffer = (int16_t*)(chunk->payload + chunk->payloadSize);
for (int i = 0; i < samples; i++) for (int i = 0; i < samples; i++)
{ {
int16_t& val = chunkBuffer[sampleFormat_.channels*i + channel]; int16_t& val = chunkBuffer[sampleFormat_.channels * i + channel];
#ifdef HAS_TREMOR #ifdef HAS_TREMOR
val = SWAP_16(clip<int16_t>(pcm[channel][i] >> 9, -32768, 32767)); val = SWAP_16(clip<int16_t>(pcm[channel][i] >> 9, -32768, 32767));
#else #else
val = SWAP_16(clip<int16_t>(floor(pcm[channel][i]*32767.f + .5f), -32768, 32767)); val = SWAP_16(clip<int16_t>(floor(pcm[channel][i] * 32767.f + .5f), -32768, 32767));
#endif #endif
} }
} }
@ -137,11 +137,11 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
int32_t* chunkBuffer = (int32_t*)(chunk->payload + chunk->payloadSize); int32_t* chunkBuffer = (int32_t*)(chunk->payload + chunk->payloadSize);
for (int i = 0; i < samples; i++) for (int i = 0; i < samples; i++)
{ {
int32_t& val = chunkBuffer[sampleFormat_.channels*i + channel]; int32_t& val = chunkBuffer[sampleFormat_.channels * i + channel];
#ifdef HAS_TREMOR #ifdef HAS_TREMOR
val = SWAP_32(clip<int32_t>(pcm[channel][i] << 7, -2147483648, 2147483647)); val = SWAP_32(clip<int32_t>(pcm[channel][i] << 7, -2147483648, 2147483647));
#else #else
val = SWAP_32(clip<int32_t>(floor(pcm[channel][i]*2147483647.f + .5f), -2147483648, 2147483647)); val = SWAP_32(clip<int32_t>(floor(pcm[channel][i] * 2147483647.f + .5f), -2147483648, 2147483647));
#endif #endif
} }
} }
@ -160,14 +160,14 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk) SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
{ {
int size = chunk->payloadSize; int size = chunk->payloadSize;
char *buffer = ogg_sync_buffer(&oy, size); char* buffer = ogg_sync_buffer(&oy, size);
memcpy(buffer, chunk->payload, size); memcpy(buffer, chunk->payload, size);
ogg_sync_wrote(&oy, size); ogg_sync_wrote(&oy, size);
if (ogg_sync_pageout(&oy, &og) != 1) if (ogg_sync_pageout(&oy, &og) != 1)
throw SnapException("Input does not appear to be an Ogg bitstream"); throw SnapException("Input does not appear to be an Ogg bitstream");
ogg_stream_init(&os,ogg_page_serialno(&og)); ogg_stream_init(&os, ogg_page_serialno(&og));
vorbis_info_init(&vi); vorbis_info_init(&vi);
vorbis_comment_init(&vc); vorbis_comment_init(&vc);
@ -185,7 +185,7 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
{ {
while (i < 2) while (i < 2)
{ {
int result=ogg_sync_pageout(&oy, &og); int result = ogg_sync_pageout(&oy, &og);
if (result == 0) if (result == 0)
break; /* Need more data */ break; /* Need more data */
/* Don't complain about missing or corrupt data yet. We'll /* Don't complain about missing or corrupt data yet. We'll
@ -195,7 +195,7 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
ogg_stream_pagein(&os, &og); /* we can ignore any errors here as they'll also become apparent at packetout */ ogg_stream_pagein(&os, &og); /* we can ignore any errors here as they'll also become apparent at packetout */
while (i < 2) while (i < 2)
{ {
result=ogg_stream_packetout(&os, &op); result = ogg_stream_packetout(&os, &op);
if (result == 0) if (result == 0)
break; break;
/// Uh oh; data at some point was corrupted or missing! /// Uh oh; data at some point was corrupted or missing!
@ -203,7 +203,7 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
if (result < 0) if (result < 0)
throw SnapException("Corrupt secondary header. Exiting."); throw SnapException("Corrupt secondary header. Exiting.");
result=vorbis_synthesis_headerin(&vi, &vc, &op); result = vorbis_synthesis_headerin(&vi, &vc, &op);
if (result < 0) if (result < 0)
throw SnapException("Corrupt secondary header. Exiting."); throw SnapException("Corrupt secondary header. Exiting.");
@ -223,13 +223,14 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
sampleFormat_.setFormat(vi.rate, 16, vi.channels); sampleFormat_.setFormat(vi.rate, 16, vi.channels);
/* Throw the comments plus a few lines about the bitstream we're decoding */ /* Throw the comments plus a few lines about the bitstream we're decoding */
char **ptr=vc.user_comments; char** ptr = vc.user_comments;
while (*ptr) while (*ptr)
{ {
std::string comment(*ptr); std::string comment(*ptr);
if (comment.find("SAMPLE_FORMAT=") == 0) if (comment.find("SAMPLE_FORMAT=") == 0)
sampleFormat_.setFormat(comment.substr(comment.find("=") + 1)); sampleFormat_.setFormat(comment.substr(comment.find("=") + 1));
LOG(INFO) << "comment: " << comment << "\n";; LOG(INFO) << "comment: " << comment << "\n";
;
++ptr; ++ptr;
} }
@ -237,6 +238,3 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
return sampleFormat_; return sampleFormat_;
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -39,8 +39,10 @@ private:
template <typename T> template <typename T>
T clip(const T& value, const T& lower, const T& upper) const T clip(const T& value, const T& lower, const T& upper) const
{ {
if (value > upper) return upper; if (value > upper)
if (value < lower) return lower; return upper;
if (value < lower)
return lower;
return value; return value;
} }
@ -59,5 +61,3 @@ private:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,10 +16,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include "common/snapException.h"
#include "common/endian.hpp"
#include "aixlog.hpp"
#include "pcmDecoder.h" #include "pcmDecoder.h"
#include "aixlog.hpp"
#include "common/endian.hpp"
#include "common/snapException.h"
#define ID_RIFF 0x46464952 #define ID_RIFF 0x46464952
@ -108,21 +108,13 @@ SampleFormat PcmDecoder::setHeader(msg::CodecHeader* chunk)
/// Unknown chunk, skip bytes /// Unknown chunk, skip bytes
pos += SWAP_32(chunk_header.sz); pos += SWAP_32(chunk_header.sz);
} }
} } while (moreChunks);
while (moreChunks);
if (SWAP_32(chunk_fmt.sample_rate) == 0) if (SWAP_32(chunk_fmt.sample_rate) == 0)
throw SnapException("Sample format not found"); throw SnapException("Sample format not found");
SampleFormat sampleFormat( SampleFormat sampleFormat(SWAP_32(chunk_fmt.sample_rate), SWAP_16(chunk_fmt.bits_per_sample), SWAP_16(chunk_fmt.num_channels));
SWAP_32(chunk_fmt.sample_rate),
SWAP_16(chunk_fmt.bits_per_sample),
SWAP_16(chunk_fmt.num_channels));
return sampleFormat; return sampleFormat;
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -31,5 +31,3 @@ public:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,8 +19,8 @@
#ifndef DOUBLE_BUFFER_H #ifndef DOUBLE_BUFFER_H
#define DOUBLE_BUFFER_H #define DOUBLE_BUFFER_H
#include<deque> #include <algorithm>
#include<algorithm> #include <deque>
/// Size limited queue /// Size limited queue
@ -63,10 +63,10 @@ public:
{ {
unsigned int low = tmpBuffer.size() / 2; unsigned int low = tmpBuffer.size() / 2;
unsigned int high = low; unsigned int high = low;
low -= mean/2; low -= mean / 2;
high += mean/2; high += mean / 2;
T result((T)0); T result((T)0);
for (unsigned int i=low; i<=high; ++i) for (unsigned int i = low; i <= high; ++i)
{ {
result += tmpBuffer[i]; result += tmpBuffer[i];
} }
@ -79,7 +79,7 @@ public:
if (buffer.empty()) if (buffer.empty())
return 0; return 0;
double mean = 0.; double mean = 0.;
for (size_t n=0; n<buffer.size(); ++n) for (size_t n = 0; n < buffer.size(); ++n)
mean += (float)buffer[n] / (float)buffer.size(); mean += (float)buffer[n] / (float)buffer.size();
return mean; return mean;
} }
@ -126,11 +126,8 @@ public:
private: private:
size_t bufferSize; size_t bufferSize;
std::deque<T> buffer; std::deque<T> buffer;
}; };
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -88,7 +88,7 @@ protected:
/* /*
* Send metadata to stderr as json * Send metadata to stderr as json
*/ */
class MetaStderrAdapter: public MetadataAdapter class MetaStderrAdapter : public MetadataAdapter
{ {
public: public:
using MetadataAdapter::push; using MetadataAdapter::push;
@ -98,7 +98,6 @@ public:
std::cerr << serialize() << "\n"; std::cerr << serialize() << "\n";
return 0; return 0;
} }
}; };
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -26,8 +26,7 @@
using namespace std; using namespace std;
AlsaPlayer::AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : AlsaPlayer::AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : Player(pcmDevice, stream), handle_(NULL), buff_(NULL)
Player(pcmDevice, stream), handle_(NULL), buff_(NULL)
{ {
} }
@ -36,7 +35,7 @@ void AlsaPlayer::initAlsa()
{ {
unsigned int tmp, rate; unsigned int tmp, rate;
int pcm, channels; int pcm, channels;
snd_pcm_hw_params_t *params; snd_pcm_hw_params_t* params;
int buff_size; int buff_size;
const SampleFormat& format = stream_->getFormat(); const SampleFormat& format = stream_->getFormat();
@ -119,9 +118,9 @@ void AlsaPlayer::initAlsa()
snd_pcm_hw_params_set_period_time_near(handle_, params, &period_time, 0); snd_pcm_hw_params_set_period_time_near(handle_, params, &period_time, 0);
snd_pcm_hw_params_set_buffer_time_near(handle_, params, &buffer_time, 0); snd_pcm_hw_params_set_buffer_time_near(handle_, params, &buffer_time, 0);
// long unsigned int periodsize = stream_->format.msRate() * 50;//2*rate/50; // long unsigned int periodsize = stream_->format.msRate() * 50;//2*rate/50;
// if ((pcm = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &periodsize)) < 0) // if ((pcm = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &periodsize)) < 0)
// LOG(ERROR) << "Unable to set buffer size " << (long int)periodsize << ": " << snd_strerror(pcm) << "\n"; // LOG(ERROR) << "Unable to set buffer size " << (long int)periodsize << ": " << snd_strerror(pcm) << "\n";
/* Write parameters */ /* Write parameters */
if ((pcm = snd_pcm_hw_params(handle_, params)) < 0) if ((pcm = snd_pcm_hw_params(handle_, params)) < 0)
@ -140,19 +139,19 @@ void AlsaPlayer::initAlsa()
snd_pcm_hw_params_get_period_size(params, &frames_, 0); snd_pcm_hw_params_get_period_size(params, &frames_, 0);
LOG(INFO) << "frames: " << frames_ << "\n"; LOG(INFO) << "frames: " << frames_ << "\n";
buff_size = frames_ * format.frameSize; //channels * 2 /* 2 -> sample size */; buff_size = frames_ * format.frameSize; // channels * 2 /* 2 -> sample size */;
buff_ = (char *) malloc(buff_size); buff_ = (char*)malloc(buff_size);
snd_pcm_hw_params_get_period_time(params, &tmp, NULL); snd_pcm_hw_params_get_period_time(params, &tmp, NULL);
LOG(DEBUG) << "period time: " << tmp << "\n"; LOG(DEBUG) << "period time: " << tmp << "\n";
snd_pcm_sw_params_t *swparams; snd_pcm_sw_params_t* swparams;
snd_pcm_sw_params_alloca(&swparams); snd_pcm_sw_params_alloca(&swparams);
snd_pcm_sw_params_current(handle_, swparams); snd_pcm_sw_params_current(handle_, swparams);
snd_pcm_sw_params_set_avail_min(handle_, swparams, frames_); snd_pcm_sw_params_set_avail_min(handle_, swparams, frames_);
snd_pcm_sw_params_set_start_threshold(handle_, swparams, frames_); snd_pcm_sw_params_set_start_threshold(handle_, swparams, frames_);
// snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_); // snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_);
snd_pcm_sw_params(handle_, swparams); snd_pcm_sw_params(handle_, swparams);
} }
@ -215,10 +214,10 @@ void AlsaPlayer::worker()
} }
} }
// snd_pcm_avail_delay(handle_, &framesAvail, &framesDelay); // snd_pcm_avail_delay(handle_, &framesAvail, &framesDelay);
snd_pcm_delay(handle_, &framesDelay); snd_pcm_delay(handle_, &framesDelay);
chronos::usec delay((chronos::usec::rep) (1000 * (double) framesDelay / stream_->getFormat().msRate())); chronos::usec delay((chronos::usec::rep)(1000 * (double)framesDelay / stream_->getFormat().msRate()));
// LOG(INFO) << "delay: " << framesDelay << ", delay[ms]: " << delay.count() / 1000 << "\n"; // LOG(INFO) << "delay: " << framesDelay << ", delay[ms]: " << delay.count() / 1000 << "\n";
if (stream_->getPlayerChunk(buff_, delay, frames_)) if (stream_->getPlayerChunk(buff_, delay, frames_))
{ {
@ -273,15 +272,18 @@ vector<PcmDevice> AlsaPlayer::pcm_list(void)
if (io != NULL && strcmp(io, "Output") != 0) if (io != NULL && strcmp(io, "Output") != 0)
goto __end; goto __end;
pcmDevice.name = name; pcmDevice.name = name;
if(descr == NULL) { if (descr == NULL)
{
pcmDevice.description = ""; pcmDevice.description = "";
} else { }
else
{
pcmDevice.description = descr; pcmDevice.description = descr;
} }
pcmDevice.idx = idx++; pcmDevice.idx = idx++;
result.push_back(pcmDevice); result.push_back(pcmDevice);
__end: __end:
if (name != NULL) if (name != NULL)
free(name); free(name);
if (descr != NULL) if (descr != NULL)
@ -293,4 +295,3 @@ __end:
snd_device_name_free_hint(hints); snd_device_name_free_hint(hints);
return result; return result;
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -49,9 +49,8 @@ private:
snd_pcm_t* handle_; snd_pcm_t* handle_;
snd_pcm_uframes_t frames_; snd_pcm_uframes_t frames_;
char *buff_; char* buff_;
}; };
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -17,26 +17,23 @@
***/ ***/
//#include <CoreServices/CoreServices.h> //#include <CoreServices/CoreServices.h>
#include <CoreAudio/CoreAudio.h>
#include "coreAudioPlayer.h" #include "coreAudioPlayer.h"
#include <CoreAudio/CoreAudio.h>
#define NUM_BUFFERS 2 #define NUM_BUFFERS 2
//http://stackoverflow.com/questions/4863811/how-to-use-audioqueue-to-play-a-sound-for-mac-osx-in-c // http://stackoverflow.com/questions/4863811/how-to-use-audioqueue-to-play-a-sound-for-mac-osx-in-c
//https://gist.github.com/andormade/1360885 // https://gist.github.com/andormade/1360885
void callback(void *custom_data, AudioQueueRef queue, AudioQueueBufferRef buffer) void callback(void* custom_data, AudioQueueRef queue, AudioQueueBufferRef buffer)
{ {
CoreAudioPlayer* player = static_cast<CoreAudioPlayer*>(custom_data); CoreAudioPlayer* player = static_cast<CoreAudioPlayer*>(custom_data);
player->playerCallback(queue, buffer); player->playerCallback(queue, buffer);
} }
CoreAudioPlayer::CoreAudioPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : CoreAudioPlayer::CoreAudioPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : Player(pcmDevice, stream), ms_(100), pubStream_(stream)
Player(pcmDevice, stream),
ms_(100),
pubStream_(stream)
{ {
} }
@ -51,13 +48,11 @@ std::vector<PcmDevice> CoreAudioPlayer::pcm_list(void)
{ {
UInt32 propsize; UInt32 propsize;
AudioObjectPropertyAddress theAddress = { kAudioHardwarePropertyDevices, AudioObjectPropertyAddress theAddress = {kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster };
AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize); AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize);
int nDevices = propsize / sizeof(AudioDeviceID); int nDevices = propsize / sizeof(AudioDeviceID);
AudioDeviceID *devids = new AudioDeviceID[nDevices]; AudioDeviceID* devids = new AudioDeviceID[nDevices];
AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids); AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids);
std::vector<PcmDevice> result; std::vector<PcmDevice> result;
@ -67,11 +62,11 @@ std::vector<PcmDevice> CoreAudioPlayer::pcm_list(void)
continue; continue;
UInt32 propSize; UInt32 propSize;
AudioObjectPropertyAddress theAddress = { kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyScopeOutput, 0 }; AudioObjectPropertyAddress theAddress = {kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyScopeOutput, 0};
if (AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propSize)) if (AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propSize))
continue; continue;
AudioBufferList *buflist = (AudioBufferList *)malloc(propSize); AudioBufferList* buflist = (AudioBufferList*)malloc(propSize);
if (AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propSize, buflist)) if (AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propSize, buflist))
continue; continue;
int channels = 0; int channels = 0;
@ -83,7 +78,7 @@ std::vector<PcmDevice> CoreAudioPlayer::pcm_list(void)
UInt32 maxlen = 1024; UInt32 maxlen = 1024;
char buf[maxlen]; char buf[maxlen];
theAddress = { kAudioDevicePropertyDeviceName, kAudioDevicePropertyScopeOutput, 0 }; theAddress = {kAudioDevicePropertyDeviceName, kAudioDevicePropertyScopeOutput, 0};
AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &maxlen, buf); AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &maxlen, buf);
LOG(DEBUG) << "device: " << i << ", name: " << buf << ", channels: " << channels << "\n"; LOG(DEBUG) << "device: " << i << ", name: " << buf << ", channels: " << channels << "\n";
@ -104,11 +99,11 @@ void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bu
size_t bufferedMs = bufferedFrames * 1000 / pubStream_->getFormat().rate + (ms_ * (NUM_BUFFERS - 1)); size_t bufferedMs = bufferedFrames * 1000 / pubStream_->getFormat().rate + (ms_ * (NUM_BUFFERS - 1));
/// 15ms DAC delay. Based on trying. /// 15ms DAC delay. Based on trying.
bufferedMs += 15; bufferedMs += 15;
// LOG(INFO) << "buffered: " << bufferedFrames << ", ms: " << bufferedMs << ", mSampleTime: " << timestamp.mSampleTime << "\n"; // LOG(INFO) << "buffered: " << bufferedFrames << ", ms: " << bufferedMs << ", mSampleTime: " << timestamp.mSampleTime << "\n";
/// TODO: sometimes this bufferedMS or AudioTimeStamp wraps around 1s (i.e. we're 1s out of sync (behind)) and recovers later on /// TODO: sometimes this bufferedMS or AudioTimeStamp wraps around 1s (i.e. we're 1s out of sync (behind)) and recovers later on
chronos::usec delay(bufferedMs * 1000); chronos::usec delay(bufferedMs * 1000);
char *buffer = (char*)bufferRef->mAudioData; char* buffer = (char*)bufferRef->mAudioData;
if (!pubStream_->getPlayerChunk(buffer, delay, frames_)) if (!pubStream_->getPlayerChunk(buffer, delay, frames_))
{ {
if (chronos::getTickCount() - lastChunkTick > 5000) if (chronos::getTickCount() - lastChunkTick > 5000)
@ -117,7 +112,7 @@ void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bu
uninitAudioQueue(queue); uninitAudioQueue(queue);
return; return;
} }
// LOG(INFO) << "Failed to get chunk. Playing silence.\n"; // LOG(INFO) << "Failed to get chunk. Playing silence.\n";
memset(buffer, 0, buff_size_); memset(buffer, 0, buff_size_);
} }
else else
@ -126,7 +121,7 @@ void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bu
adjustVolume(buffer, frames_); adjustVolume(buffer, frames_);
} }
// OSStatus status = // OSStatus status =
AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL); AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL);
if (!active_) if (!active_)
@ -163,7 +158,7 @@ void CoreAudioPlayer::initAudioQueue()
AudioStreamBasicDescription format; AudioStreamBasicDescription format;
format.mSampleRate = sampleFormat.rate; format.mSampleRate = sampleFormat.rate;
format.mFormatID = kAudioFormatLinearPCM; format.mFormatID = kAudioFormatLinearPCM;
format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;// | kAudioFormatFlagIsPacked; format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; // | kAudioFormatFlagIsPacked;
format.mBitsPerChannel = sampleFormat.bits; format.mBitsPerChannel = sampleFormat.bits;
format.mChannelsPerFrame = sampleFormat.channels; format.mChannelsPerFrame = sampleFormat.channels;
format.mBytesPerFrame = sampleFormat.frameSize; format.mBytesPerFrame = sampleFormat.frameSize;

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -58,4 +58,3 @@ protected:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,10 +19,10 @@
#include <assert.h> #include <assert.h>
#include <iostream> #include <iostream>
#include "openslPlayer.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/snapException.h" #include "common/snapException.h"
#include "common/strCompat.h" #include "common/strCompat.h"
#include "openslPlayer.h"
using namespace std; using namespace std;
@ -35,7 +35,7 @@ using namespace std;
// The documentation available is very unclear about how to best manage buffers. // The documentation available is very unclear about how to best manage buffers.
// I've chosen to this approach: Instantly enqueue a buffer that was rendered to the last time, // I've chosen to this approach: Instantly enqueue a buffer that was rendered to the last time,
// and then render the next. Hopefully it's okay to spend time in this callback after having enqueued. // and then render the next. Hopefully it's okay to spend time in this callback after having enqueued.
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
{ {
OpenslPlayer* player = static_cast<OpenslPlayer*>(context); OpenslPlayer* player = static_cast<OpenslPlayer*>(context);
player->playerCallback(bq); player->playerCallback(bq);
@ -43,19 +43,9 @@ static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
OpenslPlayer::OpenslPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : OpenslPlayer::OpenslPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream)
Player(pcmDevice, stream), : Player(pcmDevice, stream), engineObject(NULL), engineEngine(NULL), outputMixObject(NULL), bqPlayerObject(NULL), bqPlayerPlay(NULL),
engineObject(NULL), bqPlayerBufferQueue(NULL), bqPlayerVolume(NULL), curBuffer(0), ms_(50), buff_size(0), pubStream_(stream)
engineEngine(NULL),
outputMixObject(NULL),
bqPlayerObject(NULL),
bqPlayerPlay(NULL),
bqPlayerBufferQueue(NULL),
bqPlayerVolume(NULL),
curBuffer(0),
ms_(50),
buff_size(0),
pubStream_(stream)
{ {
initOpensl(); initOpensl();
} }
@ -75,33 +65,33 @@ void OpenslPlayer::playerCallback(SLAndroidSimpleBufferQueueItf bq)
return; return;
} }
/* static long lastTick = 0; /* static long lastTick = 0;
long now = chronos::getTickCount(); long now = chronos::getTickCount();
int diff = 0; int diff = 0;
if (lastTick != 0) if (lastTick != 0)
{ {
diff = now - lastTick; diff = now - lastTick;
// LOG(ERROR) << "diff: " << diff << ", frames: " << player->frames_ / 44.1 << "\n"; // LOG(ERROR) << "diff: " << diff << ", frames: " << player->frames_ / 44.1 << "\n";
// if (diff <= 50) // if (diff <= 50)
// { // {
// usleep(1000 * (50 - diff)); // usleep(1000 * (50 - diff));
// diff = 50; // diff = 50;
// } // }
} }
lastTick = chronos::getTickCount(); lastTick = chronos::getTickCount();
*/ */
// size_t d = player->frames_ / 0.48d; // size_t d = player->frames_ / 0.48d;
// LOG(ERROR) << "Delay: " << d << "\n"; // LOG(ERROR) << "Delay: " << d << "\n";
// SLAndroidSimpleBufferQueueState state; // SLAndroidSimpleBufferQueueState state;
// (*bq)->GetState(bq, &state); // (*bq)->GetState(bq, &state);
// cout << "bqPlayerCallback count: " << state.count << ", idx: " << state.index << "\n"; // cout << "bqPlayerCallback count: " << state.count << ", idx: " << state.index << "\n";
// diff = 0; // diff = 0;
// chronos::usec delay((250 - diff) * 1000); // chronos::usec delay((250 - diff) * 1000);
// while (active_ && !stream_->waitForChunk(100)) // while (active_ && !stream_->waitForChunk(100))
// LOG(INFO) << "Waiting for chunk\n"; // LOG(INFO) << "Waiting for chunk\n";
if (!active_) if (!active_)
return; return;
@ -109,7 +99,7 @@ void OpenslPlayer::playerCallback(SLAndroidSimpleBufferQueueItf bq)
chronos::usec delay(ms_ * 1000); chronos::usec delay(ms_ * 1000);
if (!pubStream_->getPlayerChunk(buffer[curBuffer], delay, frames_)) if (!pubStream_->getPlayerChunk(buffer[curBuffer], delay, frames_))
{ {
// LOG(INFO) << "Failed to get chunk. Playing silence.\n"; // LOG(INFO) << "Failed to get chunk. Playing silence.\n";
memset(buffer[curBuffer], 0, buff_size); memset(buffer[curBuffer], 0, buff_size);
} }
else else
@ -132,7 +122,7 @@ void OpenslPlayer::playerCallback(SLAndroidSimpleBufferQueueItf bq)
std::string OpenslPlayer::resultToString(SLresult result) const std::string OpenslPlayer::resultToString(SLresult result) const
{ {
switch(result) switch (result)
{ {
case SL_RESULT_SUCCESS: case SL_RESULT_SUCCESS:
return "SL_RESULT_SUCCESS"; return "SL_RESULT_SUCCESS";
@ -193,17 +183,14 @@ void OpenslPlayer::initOpensl()
const SampleFormat& format = stream_->getFormat(); const SampleFormat& format = stream_->getFormat();
frames_ = format.rate / (1000 / ms_);// * format.channels; // 1920; // 48000 * 2 / 50 // => 50ms frames_ = format.rate / (1000 / ms_); // * format.channels; // 1920; // 48000 * 2 / 50 // => 50ms
buff_size = frames_ * format.frameSize /* 2 -> sample size */; buff_size = frames_ * format.frameSize /* 2 -> sample size */;
LOG(INFO) << "frames: " << frames_ << ", channels: " << format.channels << ", rate: " << format.rate << ", buff: " << buff_size << "\n"; LOG(INFO) << "frames: " << frames_ << ", channels: " << format.channels << ", rate: " << format.rate << ", buff: " << buff_size << "\n";
SLresult result; SLresult result;
// create engine // create engine
SLEngineOption engineOption[] = SLEngineOption engineOption[] = {{(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}};
{
{(SLuint32) SL_ENGINEOPTION_THREADSAFE, (SLuint32) SL_BOOLEAN_TRUE}
};
result = slCreateEngine(&engineObject, 1, engineOption, 0, NULL, NULL); result = slCreateEngine(&engineObject, 1, engineOption, 0, NULL, NULL);
throwUnsuccess("slCreateEngine", result); throwUnsuccess("slCreateEngine", result);
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
@ -216,7 +203,7 @@ void OpenslPlayer::initOpensl()
throwUnsuccess("OutputMixObject::Realize", result); throwUnsuccess("OutputMixObject::Realize", result);
SLuint32 samplesPerSec = SL_SAMPLINGRATE_48; SLuint32 samplesPerSec = SL_SAMPLINGRATE_48;
switch(format.rate) switch (format.rate)
{ {
case 8000: case 8000:
samplesPerSec = SL_SAMPLINGRATE_8; samplesPerSec = SL_SAMPLINGRATE_8;
@ -260,7 +247,7 @@ void OpenslPlayer::initOpensl()
SLuint32 bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; SLuint32 bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
SLuint32 containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; SLuint32 containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
switch(format.bits) switch (format.bits)
{ {
case 8: case 8:
bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8; bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8;
@ -284,16 +271,9 @@ void OpenslPlayer::initOpensl()
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
SLDataFormat_PCM format_pcm = SLDataFormat_PCM format_pcm = {
{ SL_DATAFORMAT_PCM, format.channels, samplesPerSec, bitsPerSample, containerSize, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
SL_DATAFORMAT_PCM, SL_BYTEORDER_LITTLEENDIAN};
format.channels,
samplesPerSec,
bitsPerSample,
containerSize,
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
SL_BYTEORDER_LITTLEENDIAN
};
SLDataSource audioSrc = {&loc_bufq, &format_pcm}; SLDataSource audioSrc = {&loc_bufq, &format_pcm};
@ -302,8 +282,8 @@ void OpenslPlayer::initOpensl()
SLDataSink audioSnk = {&loc_outmix, NULL}; SLDataSink audioSnk = {&loc_outmix, NULL};
// create audio player // create audio player
const SLInterfaceID ids[3] = {SL_IID_ANDROIDCONFIGURATION, SL_IID_PLAY, SL_IID_BUFFERQUEUE};//, SL_IID_VOLUME}; const SLInterfaceID ids[3] = {SL_IID_ANDROIDCONFIGURATION, SL_IID_PLAY, SL_IID_BUFFERQUEUE}; //, SL_IID_VOLUME};
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};//, SL_BOOLEAN_TRUE}; const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; //, SL_BOOLEAN_TRUE};
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 3, ids, req); result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 3, ids, req);
throwUnsuccess("Engine::CreateAudioPlayer", result); throwUnsuccess("Engine::CreateAudioPlayer", result);
@ -311,7 +291,7 @@ void OpenslPlayer::initOpensl()
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDCONFIGURATION, &playerConfig); result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDCONFIGURATION, &playerConfig);
throwUnsuccess("PlayerObject::GetInterface", result); throwUnsuccess("PlayerObject::GetInterface", result);
SLint32 streamType = SL_ANDROID_STREAM_MEDIA; SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
//// SLint32 streamType = SL_ANDROID_STREAM_VOICE; //// SLint32 streamType = SL_ANDROID_STREAM_VOICE;
result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32)); result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32));
throwUnsuccess("PlayerConfig::SetConfiguration", result); throwUnsuccess("PlayerConfig::SetConfiguration", result);
@ -323,8 +303,8 @@ void OpenslPlayer::initOpensl()
throwUnsuccess("PlayerObject::GetInterface", result); throwUnsuccess("PlayerObject::GetInterface", result);
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this); result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
throwUnsuccess("PlayerBufferQueue::RegisterCallback", result); throwUnsuccess("PlayerBufferQueue::RegisterCallback", result);
// result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); // result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
// throwUnsuccess("PlayerObject::GetInterface", result); // throwUnsuccess("PlayerObject::GetInterface", result);
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED); result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);
throwUnsuccess("PlayerPlay::SetPlayState", result); throwUnsuccess("PlayerPlay::SetPlayState", result);
@ -344,8 +324,8 @@ void OpenslPlayer::initOpensl()
void OpenslPlayer::uninitOpensl() void OpenslPlayer::uninitOpensl()
{ {
// if (!active_) // if (!active_)
// return; // return;
LOG(INFO) << "uninitOpensl\n"; LOG(INFO) << "uninitOpensl\n";
SLresult result; SLresult result;
@ -385,10 +365,10 @@ void OpenslPlayer::uninitOpensl()
engineEngine = NULL; engineEngine = NULL;
} }
delete [] buffer[0]; delete[] buffer[0];
buffer[0] = NULL; buffer[0] = NULL;
delete [] buffer[1]; delete[] buffer[1];
buffer[1] = NULL; buffer[1] = NULL;
LOG(INFO) << "OpenSLWrap_Shutdown - finished\n"; LOG(INFO) << "OpenSLWrap_Shutdown - finished\n";
@ -413,4 +393,3 @@ void OpenslPlayer::stop()
void OpenslPlayer::worker() void OpenslPlayer::worker()
{ {
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -25,7 +25,7 @@
#include "player.h" #include "player.h"
typedef int (*AndroidAudioCallback)(short *buffer, int num_samples); typedef int (*AndroidAudioCallback)(short* buffer, int num_samples);
/// OpenSL Audio Player /// OpenSL Audio Player
@ -64,7 +64,7 @@ protected:
// Double buffering. // Double buffering.
int curBuffer; int curBuffer;
char *buffer[2]; char* buffer[2];
size_t ms_; size_t ms_;
size_t frames_; size_t frames_;
@ -74,4 +74,3 @@ protected:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -24,15 +24,9 @@
struct PcmDevice struct PcmDevice
{ {
PcmDevice() : PcmDevice() : idx(-1), name("default"){};
idx(-1), name("default")
{
};
PcmDevice(int idx, const std::string& name, const std::string& description = "") : PcmDevice(int idx, const std::string& name, const std::string& description = "") : idx(idx), name(name), description(description){};
idx(idx), name(name), description(description)
{
};
int idx; int idx;
std::string name; std::string name;
@ -41,4 +35,3 @@ struct PcmDevice
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,23 +16,18 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <iostream>
#include <cmath> #include <cmath>
#include <iostream>
#include "player.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "player.h"
using namespace std; using namespace std;
Player::Player(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : Player::Player(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream)
active_(false), : active_(false), stream_(stream), pcmDevice_(pcmDevice), volume_(1.0), muted_(false), volCorrection_(1.0)
stream_(stream),
pcmDevice_(pcmDevice),
volume_(1.0),
muted_(false),
volCorrection_(1.0)
{ {
} }
@ -74,18 +69,18 @@ void Player::adjustVolume(char* buffer, size_t frames)
{ {
volume *= volCorrection_; volume *= volCorrection_;
if (sampleFormat.sampleSize == 1) if (sampleFormat.sampleSize == 1)
adjustVolume<int8_t>(buffer, frames*sampleFormat.channels, volume); adjustVolume<int8_t>(buffer, frames * sampleFormat.channels, volume);
else if (sampleFormat.sampleSize == 2) else if (sampleFormat.sampleSize == 2)
adjustVolume<int16_t>(buffer, frames*sampleFormat.channels, volume); adjustVolume<int16_t>(buffer, frames * sampleFormat.channels, volume);
else if (sampleFormat.sampleSize == 4) else if (sampleFormat.sampleSize == 4)
adjustVolume<int32_t>(buffer, frames*sampleFormat.channels, volume); adjustVolume<int32_t>(buffer, frames * sampleFormat.channels, volume);
} }
} }
//https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/src/pulse/volume.c#n260 // https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/src/pulse/volume.c#n260
//http://www.robotplanet.dk/audio/audio_gui_design/ // http://www.robotplanet.dk/audio/audio_gui_design/
//https://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#22198 // https://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#22198
void Player::setVolume_poly(double volume, double exp) void Player::setVolume_poly(double volume, double exp)
{ {
volume_ = std::pow(volume, exp); volume_ = std::pow(volume, exp);
@ -93,12 +88,12 @@ void Player::setVolume_poly(double volume, double exp)
} }
//http://stackoverflow.com/questions/1165026/what-algorithms-could-i-use-for-audio-volume-level // http://stackoverflow.com/questions/1165026/what-algorithms-could-i-use-for-audio-volume-level
void Player::setVolume_exp(double volume, double base) void Player::setVolume_exp(double volume, double base)
{ {
// double base = M_E; // double base = M_E;
// double base = 10.; // double base = 10.;
volume_ = (pow(base, volume)-1) / (base-1); volume_ = (pow(base, volume) - 1) / (base - 1);
LOG(DEBUG) << "setVolume exp: " << volume << " => " << volume_ << "\n"; LOG(DEBUG) << "setVolume exp: " << volume << " => " << volume_ << "\n";
} }
@ -113,5 +108,3 @@ void Player::setMute(bool mute)
{ {
muted_ = mute; muted_ = mute;
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,14 +19,14 @@
#ifndef PLAYER_H #ifndef PLAYER_H
#define PLAYER_H #define PLAYER_H
#include "aixlog.hpp"
#include "common/endian.hpp"
#include "pcmDevice.h"
#include "stream.h"
#include <atomic>
#include <string> #include <string>
#include <thread> #include <thread>
#include <atomic>
#include <vector> #include <vector>
#include "stream.h"
#include "pcmDevice.h"
#include "common/endian.hpp"
#include "aixlog.hpp"
/// Audio Player /// Audio Player
@ -52,10 +52,10 @@ protected:
void setVolume_exp(double volume, double base); void setVolume_exp(double volume, double base);
template <typename T> template <typename T>
void adjustVolume(char *buffer, size_t count, double volume) void adjustVolume(char* buffer, size_t count, double volume)
{ {
T* bufferT = (T*)buffer; T* bufferT = (T*)buffer;
for (size_t n=0; n<count; ++n) for (size_t n = 0; n < count; ++n)
bufferT[n] = endian::swap<T>(endian::swap<T>(bufferT[n]) * volume); bufferT[n] = endian::swap<T>(endian::swap<T>(bufferT[n]) * volume);
} }
@ -72,4 +72,3 @@ protected:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,9 +19,9 @@
#include <iostream> #include <iostream>
#include <sys/resource.h> #include <sys/resource.h>
#include "popl.hpp"
#include "controller.h"
#include "browseZeroConf/browsemDNS.h" #include "browseZeroConf/browsemDNS.h"
#include "controller.h"
#include "popl.hpp"
#ifdef HAS_ALSA #ifdef HAS_ALSA
#include "player/alsaPlayer.h" #include "player/alsaPlayer.h"
@ -49,15 +49,15 @@ PcmDevice getPcmDevice(const std::string& soundcard)
try try
{ {
int soundcardIdx = cpt::stoi(soundcard); int soundcardIdx = cpt::stoi(soundcard);
for (auto dev: pcmDevices) for (auto dev : pcmDevices)
if (dev.idx == soundcardIdx) if (dev.idx == soundcardIdx)
return dev; return dev;
} }
catch(...) catch (...)
{ {
} }
for (auto dev: pcmDevices) for (auto dev : pcmDevices)
if (dev.name.find(soundcard) != string::npos) if (dev.name.find(soundcard) != string::npos)
return dev; return dev;
#endif #endif
@ -66,7 +66,7 @@ PcmDevice getPcmDevice(const std::string& soundcard)
} }
int main (int argc, char **argv) int main(int argc, char** argv)
{ {
#ifdef MACOS #ifdef MACOS
#pragma message "Warning: the macOS support is experimental and might not be maintained" #pragma message "Warning: the macOS support is experimental and might not be maintained"
@ -88,19 +88,19 @@ int main (int argc, char **argv)
auto versionSwitch = op.add<Switch>("v", "version", "show version number"); auto versionSwitch = op.add<Switch>("v", "version", "show version number");
#if defined(HAS_ALSA) #if defined(HAS_ALSA)
auto listSwitch = op.add<Switch>("l", "list", "list pcm devices"); auto listSwitch = op.add<Switch>("l", "list", "list pcm devices");
/*auto soundcardValue =*/ op.add<Value<string>>("s", "soundcard", "index or name of the soundcard", "default", &soundcard); /*auto soundcardValue =*/op.add<Value<string>>("s", "soundcard", "index or name of the soundcard", "default", &soundcard);
#endif #endif
auto metaStderr = op.add<Switch>("e", "mstderr", "send metadata to stderr"); auto metaStderr = op.add<Switch>("e", "mstderr", "send metadata to stderr");
//auto metaHook = op.add<Value<string>>("m", "mhook", "script to call on meta tags", "", &meta_script); // auto metaHook = op.add<Value<string>>("m", "mhook", "script to call on meta tags", "", &meta_script);
/*auto hostValue =*/ op.add<Value<string>>("h", "host", "server hostname or ip address", "", &host); /*auto hostValue =*/op.add<Value<string>>("h", "host", "server hostname or ip address", "", &host);
/*auto portValue =*/ op.add<Value<size_t>>("p", "port", "server port", 1704, &port); /*auto portValue =*/op.add<Value<size_t>>("p", "port", "server port", 1704, &port);
#ifdef HAS_DAEMON #ifdef HAS_DAEMON
int processPriority(-3); int processPriority(-3);
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "daemonize, optional process priority [-20..19]", -3, &processPriority); auto daemonOption = op.add<Implicit<int>>("d", "daemon", "daemonize, optional process priority [-20..19]", -3, &processPriority);
auto userValue = op.add<Value<string>>("", "user", "the user[:group] to run snapclient as when daemonized"); auto userValue = op.add<Value<string>>("", "user", "the user[:group] to run snapclient as when daemonized");
#endif #endif
/*auto latencyValue =*/ op.add<Value<int>>("", "latency", "latency of the soundcard", 0, &latency); /*auto latencyValue =*/op.add<Value<int>>("", "latency", "latency of the soundcard", 0, &latency);
/*auto instanceValue =*/ op.add<Value<size_t>>("i", "instance", "instance id", 1, &instance); /*auto instanceValue =*/op.add<Value<size_t>>("i", "instance", "instance id", 1, &instance);
auto hostIdValue = op.add<Value<string>>("", "hostID", "unique host id", ""); auto hostIdValue = op.add<Value<string>>("", "hostID", "unique host id", "");
try try
@ -129,10 +129,9 @@ int main (int argc, char **argv)
if (listSwitch->is_set()) if (listSwitch->is_set())
{ {
vector<PcmDevice> pcmDevices = AlsaPlayer::pcm_list(); vector<PcmDevice> pcmDevices = AlsaPlayer::pcm_list();
for (auto dev: pcmDevices) for (auto dev : pcmDevices)
{ {
cout << dev.idx << ": " << dev.name << "\n" cout << dev.idx << ": " << dev.name << "\n" << dev.description << "\n\n";
<< dev.description << "\n\n";
} }
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
@ -162,7 +161,8 @@ int main (int argc, char **argv)
{ {
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
if (!debugOption->value().empty()) if (!debugOption->value().empty())
AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(), "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(),
"%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
} }
else else
{ {
@ -211,7 +211,7 @@ int main (int argc, char **argv)
if (pcmDevice.idx == -1) if (pcmDevice.idx == -1)
{ {
cout << "soundcard \"" << soundcard << "\" not found\n"; cout << "soundcard \"" << soundcard << "\" not found\n";
// exit(EXIT_FAILURE); // exit(EXIT_FAILURE);
} }
#endif #endif
@ -246,7 +246,7 @@ int main (int argc, char **argv)
// Setup metadata handling // Setup metadata handling
std::shared_ptr<MetadataAdapter> meta; std::shared_ptr<MetadataAdapter> meta;
meta.reset(new MetadataAdapter); meta.reset(new MetadataAdapter);
if(metaStderr) if (metaStderr)
meta.reset(new MetaStderrAdapter); meta.reset(new MetaStderrAdapter);
std::unique_ptr<Controller> controller(new Controller(hostIdValue->value(), instance, meta)); std::unique_ptr<Controller> controller(new Controller(hostIdValue->value(), instance, meta));
@ -254,7 +254,7 @@ int main (int argc, char **argv)
{ {
LOG(INFO) << "Latency: " << latency << "\n"; LOG(INFO) << "Latency: " << latency << "\n";
controller->start(pcmDevice, host, port, latency); controller->start(pcmDevice, host, port, latency);
while(!g_terminated) while (!g_terminated)
chronos::sleep(100); chronos::sleep(100);
controller->stop(); controller->stop();
} }
@ -268,5 +268,3 @@ int main (int argc, char **argv)
SLOG(NOTICE) << "daemon terminated." << endl; SLOG(NOTICE) << "daemon terminated." << endl;
exit(exitcode); exit(exitcode);
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -17,31 +17,32 @@
***/ ***/
#include "stream.h" #include "stream.h"
#include "aixlog.hpp"
#include "timeProvider.h"
#include <cmath> #include <cmath>
#include <iostream> #include <iostream>
#include <string.h> #include <string.h>
#include "aixlog.hpp"
#include "timeProvider.h"
using namespace std; using namespace std;
//using namespace chronos; // using namespace chronos;
namespace cs = chronos; namespace cs = chronos;
Stream::Stream(const SampleFormat& sampleFormat) : format_(sampleFormat), sleep_(0), median_(0), shortMedian_(0), lastUpdate_(0), playedFrames_(0), bufferMs_(cs::msec(500)) Stream::Stream(const SampleFormat& sampleFormat)
: format_(sampleFormat), sleep_(0), median_(0), shortMedian_(0), lastUpdate_(0), playedFrames_(0), bufferMs_(cs::msec(500))
{ {
buffer_.setSize(500); buffer_.setSize(500);
shortBuffer_.setSize(100); shortBuffer_.setSize(100);
miniBuffer_.setSize(20); miniBuffer_.setSize(20);
// cardBuffer_.setSize(50); // cardBuffer_.setSize(50);
/* /*
48000 x 48000 x
------- = ----- ------- = -----
47999,2 x - 1 47999,2 x - 1
x = 1,000016667 / (1,000016667 - 1) x = 1,000016667 / (1,000016667 - 1)
*/ */
setRealSampleRate(format_.rate); setRealSampleRate(format_.rate);
} }
@ -52,7 +53,7 @@ void Stream::setRealSampleRate(double sampleRate)
correctAfterXFrames_ = 0; correctAfterXFrames_ = 0;
else else
correctAfterXFrames_ = round((format_.rate / sampleRate) / (format_.rate / sampleRate - 1.)); correctAfterXFrames_ = round((format_.rate / sampleRate) / (format_.rate / sampleRate - 1.));
// LOG(DEBUG) << "Correct after X: " << correctAfterXFrames_ << " (Real rate: " << sampleRate << ", rate: " << format_.rate << ")\n"; // LOG(DEBUG) << "Correct after X: " << correctAfterXFrames_ << " (Real rate: " << sampleRate << ", rate: " << format_.rate << ")\n";
} }
@ -76,7 +77,7 @@ void Stream::addChunk(msg::PcmChunk* chunk)
while (chunks_.size() * chunk->duration<cs::msec>().count() > 10000) while (chunks_.size() * chunk->duration<cs::msec>().count() > 10000)
chunks_.pop(); chunks_.pop();
chunks_.push(shared_ptr<msg::PcmChunk>(chunk)); chunks_.push(shared_ptr<msg::PcmChunk>(chunk));
// LOG(DEBUG) << "new chunk: " << chunk->duration<cs::msec>().count() << ", Chunks: " << chunks_.size() << "\n"; // LOG(DEBUG) << "new chunk: " << chunk->duration<cs::msec>().count() << ", Chunks: " << chunks_.size() << "\n";
} }
@ -145,7 +146,7 @@ cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec
unsigned long read = 0; unsigned long read = 0;
while (read < framesPerBuffer) while (read < framesPerBuffer)
{ {
read += chunk_->readFrames(buffer + read*format_.frameSize, framesPerBuffer - read); read += chunk_->readFrames(buffer + read * format_.frameSize, framesPerBuffer - read);
if (chunk_->isEndOfChunk() && !chunks_.try_pop(chunk_, timeout)) if (chunk_->isEndOfChunk() && !chunks_.try_pop(chunk_, timeout))
throw 0; throw 0;
} }
@ -162,14 +163,14 @@ cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec
char* buffer = (char*)malloc(toRead * format_.frameSize); char* buffer = (char*)malloc(toRead * format_.frameSize);
cs::time_point_clk tp = getNextPlayerChunk(buffer, timeout, toRead); cs::time_point_clk tp = getNextPlayerChunk(buffer, timeout, toRead);
float factor = (float)toRead / framesPerBuffer;//(float)(framesPerBuffer*channels_); float factor = (float)toRead / framesPerBuffer; //(float)(framesPerBuffer*channels_);
// if (abs(framesCorrection) > 1) // if (abs(framesCorrection) > 1)
// LOG(INFO) << "correction: " << framesCorrection << ", factor: " << factor << "\n"; // LOG(INFO) << "correction: " << framesCorrection << ", factor: " << factor << "\n";
float idx = 0; float idx = 0;
for (size_t n=0; n<framesPerBuffer; ++n) for (size_t n = 0; n < framesPerBuffer; ++n)
{ {
size_t index(floor(idx));// = (int)(ceil(n*factor)); size_t index(floor(idx)); // = (int)(ceil(n*factor));
memcpy((char*)outputBuffer + n*format_.frameSize, buffer + index*format_.frameSize, format_.frameSize); memcpy((char*)outputBuffer + n * format_.frameSize, buffer + index * format_.frameSize, format_.frameSize);
idx += factor; idx += factor;
} }
free(buffer); free(buffer);
@ -207,7 +208,7 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
if (!chunk_ && !chunks_.try_pop(chunk_, outputBufferDacTime)) if (!chunk_ && !chunks_.try_pop(chunk_, outputBufferDacTime))
{ {
//LOG(INFO) << "no chunks available\n"; // LOG(INFO) << "no chunks available\n";
sleep_ = cs::usec(0); sleep_ = cs::usec(0);
return false; return false;
} }
@ -220,7 +221,7 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
/// age < 0 => play in -age /// age < 0 => play in -age
/// age > 0 => too old /// age > 0 => too old
cs::usec age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start()) - bufferMs_ + outputBufferDacTime; cs::usec age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start()) - bufferMs_ + outputBufferDacTime;
// LOG(INFO) << "age: " << age.count() / 1000 << "\n"; // LOG(INFO) << "age: " << age.count() / 1000 << "\n";
if ((sleep_.count() == 0) && (cs::abs(age) > cs::msec(200))) if ((sleep_.count() == 0) && (cs::abs(age) > cs::msec(200)))
{ {
LOG(INFO) << "age > 200: " << cs::duration<cs::msec>(age) << "\n"; LOG(INFO) << "age > 200: " << cs::duration<cs::msec>(age) << "\n";
@ -230,30 +231,33 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
try try
{ {
//LOG(DEBUG) << "framesPerBuffer: " << framesPerBuffer << "\tms: " << framesPerBuffer*2 / PLAYER_CHUNK_MS_SIZE << "\t" << PLAYER_CHUNK_SIZE << "\n"; // LOG(DEBUG) << "framesPerBuffer: " << framesPerBuffer << "\tms: " << framesPerBuffer*2 / PLAYER_CHUNK_MS_SIZE << "\t" << PLAYER_CHUNK_SIZE << "\n";
cs::nsec bufferDuration = cs::nsec(cs::nsec::rep(framesPerBuffer / format_.nsRate())); cs::nsec bufferDuration = cs::nsec(cs::nsec::rep(framesPerBuffer / format_.nsRate()));
// LOG(DEBUG) << "buffer duration: " << bufferDuration.count() << "\n"; // LOG(DEBUG) << "buffer duration: " << bufferDuration.count() << "\n";
cs::usec correction = cs::usec(0); cs::usec correction = cs::usec(0);
if (sleep_.count() != 0) if (sleep_.count() != 0)
{ {
resetBuffers(); resetBuffers();
if (sleep_ < -bufferDuration/2) if (sleep_ < -bufferDuration / 2)
{ {
LOG(INFO) << "sleep < -bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " < " << -cs::duration<cs::msec>(bufferDuration)/2 << ", "; LOG(INFO) << "sleep < -bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " < " << -cs::duration<cs::msec>(bufferDuration) / 2 << ", ";
// We're early: not enough chunks_. play silence. Reference chunk_ is the oldest (front) one // We're early: not enough chunks_. play silence. Reference chunk_ is the oldest (front) one
sleep_ = chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - getSilentPlayerChunk(outputBuffer, framesPerBuffer) - bufferMs_ + outputBufferDacTime); sleep_ = chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - getSilentPlayerChunk(outputBuffer, framesPerBuffer) - bufferMs_ +
outputBufferDacTime);
LOG(INFO) << "sleep: " << cs::duration<cs::msec>(sleep_) << "\n"; LOG(INFO) << "sleep: " << cs::duration<cs::msec>(sleep_) << "\n";
if (sleep_ < -bufferDuration/2) if (sleep_ < -bufferDuration / 2)
return true; return true;
} }
else if (sleep_ > bufferDuration/2) else if (sleep_ > bufferDuration / 2)
{ {
LOG(INFO) << "sleep > bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " > " << cs::duration<cs::msec>(bufferDuration)/2 << "\n"; LOG(INFO) << "sleep > bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " > " << cs::duration<cs::msec>(bufferDuration) / 2 << "\n";
// We're late: discard oldest chunks // We're late: discard oldest chunks
while (sleep_ > chunk_->duration<cs::usec>()) while (sleep_ > chunk_->duration<cs::usec>())
{ {
LOG(INFO) << "sleep > chunkDuration: " << cs::duration<cs::msec>(sleep_) << " > " << chunk_->duration<cs::msec>().count() << ", chunks: " << chunks_.size() << ", out: " << cs::duration<cs::msec>(outputBufferDacTime) << ", needed: " << cs::duration<cs::msec>(bufferDuration) << "\n"; LOG(INFO) << "sleep > chunkDuration: " << cs::duration<cs::msec>(sleep_) << " > " << chunk_->duration<cs::msec>().count()
<< ", chunks: " << chunks_.size() << ", out: " << cs::duration<cs::msec>(outputBufferDacTime)
<< ", needed: " << cs::duration<cs::msec>(bufferDuration) << "\n";
sleep_ = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start() - bufferMs_ + outputBufferDacTime); sleep_ = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start() - bufferMs_ + outputBufferDacTime);
if (!chunks_.try_pop(chunk_, outputBufferDacTime)) if (!chunks_.try_pop(chunk_, outputBufferDacTime))
{ {
@ -285,16 +289,18 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
} }
// framesCorrection = number of frames to be read more or less to get in-sync // framesCorrection = number of frames to be read more or less to get in-sync
long framesCorrection = correction.count()*format_.usRate(); long framesCorrection = correction.count() * format_.usRate();
// sample rate correction // sample rate correction
if ((correctAfterXFrames_ != 0) && (playedFrames_ >= (unsigned long)abs(correctAfterXFrames_))) if ((correctAfterXFrames_ != 0) && (playedFrames_ >= (unsigned long)abs(correctAfterXFrames_)))
{ {
framesCorrection += (correctAfterXFrames_ > 0)?1:-1; framesCorrection += (correctAfterXFrames_ > 0) ? 1 : -1;
playedFrames_ -= abs(correctAfterXFrames_); playedFrames_ -= abs(correctAfterXFrames_);
} }
age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - getNextPlayerChunk(outputBuffer, outputBufferDacTime, framesPerBuffer, framesCorrection) - bufferMs_ + outputBufferDacTime); age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() -
getNextPlayerChunk(outputBuffer, outputBufferDacTime, framesPerBuffer, framesCorrection) - bufferMs_ +
outputBufferDacTime);
setRealSampleRate(format_.rate); setRealSampleRate(format_.rate);
if (sleep_.count() == 0) if (sleep_.count() == 0)
@ -315,8 +321,8 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
setRealSampleRate(format_.rate + format_.rate / 1000); setRealSampleRate(format_.rate + format_.rate / 1000);
} }
*/ } */ }
else if (shortBuffer_.full()) else if (shortBuffer_.full())
{ {
if (cs::usec(abs(shortMedian_)) > cs::msec(5)) if (cs::usec(abs(shortMedian_)) > cs::msec(5))
{ {
LOG(INFO) << "pShortBuffer->full() && (abs(shortMedian_) > 5): " << shortMedian_ << "\n"; LOG(INFO) << "pShortBuffer->full() && (abs(shortMedian_) > 5): " << shortMedian_ << "\n";
@ -327,11 +333,11 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
setRealSampleRate(format_.rate + -shortMedian_ / 100); setRealSampleRate(format_.rate + -shortMedian_ / 100);
} }
*/ } */ }
else if (miniBuffer_.full() && (cs::usec(abs(miniBuffer_.median())) > cs::msec(50))) else if (miniBuffer_.full() && (cs::usec(abs(miniBuffer_.median())) > cs::msec(50)))
{ {
LOG(INFO) << "pMiniBuffer->full() && (abs(pMiniBuffer->mean()) > 50): " << miniBuffer_.median() << "\n"; LOG(INFO) << "pMiniBuffer->full() && (abs(pMiniBuffer->mean()) > 50): " << miniBuffer_.median() << "\n";
sleep_ = cs::usec((cs::msec::rep)miniBuffer_.mean()); sleep_ = cs::usec((cs::msec::rep)miniBuffer_.mean());
} }
} }
if (sleep_.count() != 0) if (sleep_.count() != 0)
@ -341,7 +347,8 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
if (lastAge != msAge) if (lastAge != msAge)
{ {
lastAge = msAge; lastAge = msAge;
LOG(INFO) << "Sleep " << cs::duration<cs::msec>(sleep_) << ", age: " << msAge << ", bufferDuration: " << cs::duration<cs::msec>(bufferDuration) << "\n"; LOG(INFO) << "Sleep " << cs::duration<cs::msec>(sleep_) << ", age: " << msAge << ", bufferDuration: " << cs::duration<cs::msec>(bufferDuration)
<< "\n";
} }
} }
else if (shortBuffer_.full()) else if (shortBuffer_.full())
@ -361,17 +368,16 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
lastUpdate_ = now; lastUpdate_ = now;
median_ = buffer_.median(); median_ = buffer_.median();
shortMedian_ = shortBuffer_.median(); shortMedian_ = shortBuffer_.median();
LOG(INFO) << "Chunk: " << age.count()/100 << "\t" << miniBuffer_.median()/100 << "\t" << shortMedian_/100 << "\t" << median_/100 << "\t" << buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n"; LOG(INFO) << "Chunk: " << age.count() / 100 << "\t" << miniBuffer_.median() / 100 << "\t" << shortMedian_ / 100 << "\t" << median_ / 100 << "\t"
// LOG(INFO) << "Chunk: " << age.count()/1000 << "\t" << miniBuffer_.median()/1000 << "\t" << shortMedian_/1000 << "\t" << median_/1000 << "\t" << buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n"; << buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n";
// LOG(INFO) << "Chunk: " << age.count()/1000 << "\t" << miniBuffer_.median()/1000 << "\t" << shortMedian_/1000 << "\t" << median_/1000 << "\t"
//<< buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n";
} }
return (abs(cs::duration<cs::msec>(age)) < 500); return (abs(cs::duration<cs::msec>(age)) < 500);
} }
catch(int e) catch (int e)
{ {
sleep_ = cs::usec(0); sleep_ = cs::usec(0);
return false; return false;
} }
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -25,13 +25,13 @@
//#include <chrono> //#include <chrono>
//#include "common/timeUtils.h" //#include "common/timeUtils.h"
#include <deque> #include "common/queue.h"
#include <memory> #include "common/sampleFormat.h"
#include "doubleBuffer.h" #include "doubleBuffer.h"
#include "message/message.h" #include "message/message.h"
#include "message/pcmChunk.h" #include "message/pcmChunk.h"
#include "common/sampleFormat.h" #include <deque>
#include "common/queue.h" #include <memory>
/// Time synchronized audio stream /// Time synchronized audio stream
@ -67,7 +67,7 @@ private:
chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long framesPerBuffer, long framesCorrection); chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long framesPerBuffer, long framesCorrection);
chronos::time_point_clk getSilentPlayerChunk(void* outputBuffer, unsigned long framesPerBuffer); chronos::time_point_clk getSilentPlayerChunk(void* outputBuffer, unsigned long framesPerBuffer);
chronos::time_point_clk seek(long ms); chronos::time_point_clk seek(long ms);
// time_point_ms seekTo(const time_point_ms& to); // time_point_ms seekTo(const time_point_ms& to);
void updateBuffers(int age); void updateBuffers(int age);
void resetBuffers(); void resetBuffers();
void setRealSampleRate(double sampleRate); void setRealSampleRate(double sampleRate);
@ -77,7 +77,7 @@ private:
chronos::usec sleep_; chronos::usec sleep_;
Queue<std::shared_ptr<msg::PcmChunk>> chunks_; Queue<std::shared_ptr<msg::PcmChunk>> chunks_;
// DoubleBuffer<chronos::usec::rep> cardBuffer; // DoubleBuffer<chronos::usec::rep> cardBuffer;
DoubleBuffer<chronos::usec::rep> miniBuffer_; DoubleBuffer<chronos::usec::rep> miniBuffer_;
DoubleBuffer<chronos::usec::rep> buffer_; DoubleBuffer<chronos::usec::rep> buffer_;
DoubleBuffer<chronos::usec::rep> shortBuffer_; DoubleBuffer<chronos::usec::rep> shortBuffer_;
@ -94,5 +94,3 @@ private:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -28,8 +28,8 @@ TimeProvider::TimeProvider() : diffToServer_(0)
void TimeProvider::setDiff(const tv& c2s, const tv& s2c) void TimeProvider::setDiff(const tv& c2s, const tv& s2c)
{ {
// tv latency = c2s - s2c; // tv latency = c2s - s2c;
// double diff = (latency.sec * 1000. + latency.usec / 1000.) / 2.; // double diff = (latency.sec * 1000. + latency.usec / 1000.) / 2.;
double diff = ((double)c2s.sec / 2. - (double)s2c.sec / 2.) * 1000. + ((double)c2s.usec / 2. - (double)s2c.usec / 2.) / 1000.; double diff = ((double)c2s.sec / 2. - (double)s2c.sec / 2.) * 1000. + ((double)c2s.usec / 2. - (double)s2c.usec / 2.) / 1000.;
setDiffToServer(diff); setDiffToServer(diff);
} }
@ -45,14 +45,14 @@ void TimeProvider::setDiffToServer(double ms)
if (!diffBuffer_.empty() && (std::abs(now.tv_sec - lastTimeSync) > 60)) if (!diffBuffer_.empty() && (std::abs(now.tv_sec - lastTimeSync) > 60))
{ {
LOG(INFO) << "Last time sync older than a minute. Clearing time buffer\n"; LOG(INFO) << "Last time sync older than a minute. Clearing time buffer\n";
diffToServer_ = ms*1000; diffToServer_ = ms * 1000;
diffBuffer_.clear(); diffBuffer_.clear();
} }
lastTimeSync = now.tv_sec; lastTimeSync = now.tv_sec;
diffBuffer_.add(ms*1000); diffBuffer_.add(ms * 1000);
diffToServer_ = diffBuffer_.median(3); diffToServer_ = diffBuffer_.median(3);
// LOG(INFO) << "setDiffToServer: " << ms << ", diff: " << diffToServer_ / 1000.f << "\n"; // LOG(INFO) << "setDiffToServer: " << ms << ", diff: " << diffToServer_ / 1000.f << "\n";
} }
/* /*
@ -61,4 +61,3 @@ long TimeProvider::getPercentileDiffToServer(size_t percentile)
return diffBuffer.percentile(percentile); return diffBuffer.percentile(percentile);
} }
*/ */

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,11 +19,11 @@
#ifndef TIME_PROVIDER_H #ifndef TIME_PROVIDER_H
#define TIME_PROVIDER_H #define TIME_PROVIDER_H
#include <atomic> #include "common/timeDefs.h"
#include <chrono>
#include "doubleBuffer.h" #include "doubleBuffer.h"
#include "message/message.h" #include "message/message.h"
#include "common/timeDefs.h" #include <atomic>
#include <chrono>
/// Provides local and server time /// Provides local and server time
@ -44,18 +44,18 @@ public:
void setDiffToServer(double ms); void setDiffToServer(double ms);
void setDiff(const tv& c2s, const tv& s2c); void setDiff(const tv& c2s, const tv& s2c);
template<typename T> template <typename T>
inline T getDiffToServer() const inline T getDiffToServer() const
{ {
return std::chrono::duration_cast<T>(chronos::usec(diffToServer_)); return std::chrono::duration_cast<T>(chronos::usec(diffToServer_));
} }
/* chronos::usec::rep getDiffToServer(); /* chronos::usec::rep getDiffToServer();
chronos::usec::rep getPercentileDiffToServer(size_t percentile); chronos::usec::rep getPercentileDiffToServer(size_t percentile);
long getDiffToServerMs(); long getDiffToServerMs();
*/ */
template<typename T> template <typename T>
static T sinceEpoche(const chronos::time_point_clk& point) static T sinceEpoche(const chronos::time_point_clk& point)
{ {
return std::chrono::duration_cast<T>(point.time_since_epoch()); return std::chrono::duration_cast<T>(point.time_since_epoch());
@ -87,5 +87,3 @@ private:
#endif #endif

View file

@ -1,5 +1,5 @@
# This file is part of snapcast # This file is part of snapcast
# Copyright (C) 2014-2018 Johannes Pohl # Copyright (C) 2014-2019 Johannes Pohl
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -18,27 +18,24 @@
#include "daemon.h" #include "daemon.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <iostream>
#include "common/snapException.h" #include "common/snapException.h"
#include "common/strCompat.h" #include "common/strCompat.h"
#include "common/utils/file_utils.h"
#include "common/utils.h" #include "common/utils.h"
#include "common/utils/file_utils.h"
#include <fcntl.h>
#include <grp.h>
#include <iostream>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>
Daemon::Daemon(const std::string& user, const std::string& group, const std::string& pidfile) : Daemon::Daemon(const std::string& user, const std::string& group, const std::string& pidfile)
pidFilehandle_(-1), : pidFilehandle_(-1), user_(user), group_(group), pidfile_(pidfile)
user_(user),
group_(group),
pidfile_(pidfile)
{ {
if (pidfile.empty() || pidfile.find('/') == std::string::npos) if (pidfile.empty() || pidfile.find('/') == std::string::npos)
throw SnapException("invalid pid file \"" + pidfile + "\""); throw SnapException("invalid pid file \"" + pidfile + "\"");
@ -58,8 +55,8 @@ void Daemon::daemonize()
utils::file::mkdirRecursive(pidfileDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); utils::file::mkdirRecursive(pidfileDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
/// Ensure only one copy /// Ensure only one copy
pidFilehandle_ = open(pidfile_.c_str(), O_RDWR|O_CREAT, 0644); pidFilehandle_ = open(pidfile_.c_str(), O_RDWR | O_CREAT, 0644);
if (pidFilehandle_ == -1 ) if (pidFilehandle_ == -1)
{ {
/// Couldn't open lock file /// Couldn't open lock file
throw SnapException("Could not open PID lock file \"" + pidfile_ + "\""); throw SnapException("Could not open PID lock file \"" + pidfile_ + "\"");
@ -68,13 +65,13 @@ void Daemon::daemonize()
uid_t user_uid = (uid_t)-1; uid_t user_uid = (uid_t)-1;
gid_t user_gid = (gid_t)-1; gid_t user_gid = (gid_t)-1;
std::string user_name; std::string user_name;
//#ifdef FREEBSD //#ifdef FREEBSD
// bool had_group = false; // bool had_group = false;
//#endif //#endif
if (!user_.empty()) if (!user_.empty())
{ {
struct passwd *pwd = getpwnam(user_.c_str()); struct passwd* pwd = getpwnam(user_.c_str());
if (pwd == nullptr) if (pwd == nullptr)
throw SnapException("no such user \"" + user_ + "\""); throw SnapException("no such user \"" + user_ + "\"");
user_uid = pwd->pw_uid; user_uid = pwd->pw_uid;
@ -86,13 +83,13 @@ void Daemon::daemonize()
if (!group_.empty()) if (!group_.empty())
{ {
struct group *grp = getgrnam(group_.c_str()); struct group* grp = getgrnam(group_.c_str());
if (grp == nullptr) if (grp == nullptr)
throw SnapException("no such group \"" + group_ + "\""); throw SnapException("no such group \"" + group_ + "\"");
user_gid = grp->gr_gid; user_gid = grp->gr_gid;
//#ifdef FREEBSD //#ifdef FREEBSD
// had_group = true; // had_group = true;
//#endif //#endif
} }
if (chown(pidfile_.c_str(), user_uid, user_gid) == -1) if (chown(pidfile_.c_str(), user_uid, user_gid) == -1)
@ -105,14 +102,14 @@ void Daemon::daemonize()
if (user_gid != (gid_t)-1 && user_gid != getgid() && setgid(user_gid) == -1) if (user_gid != (gid_t)-1 && user_gid != getgid() && setgid(user_gid) == -1)
throw SnapException("Failed to set group " + cpt::to_string((int)user_gid)); throw SnapException("Failed to set group " + cpt::to_string((int)user_gid));
//#if defined(FREEBSD) && !defined(MACOS) //#if defined(FREEBSD) && !defined(MACOS)
//#ifdef FREEBSD //#ifdef FREEBSD
/// init supplementary groups /// init supplementary groups
/// (must be done before we change our uid) /// (must be done before we change our uid)
/// no need to set the new user's supplementary groups if we are already this user /// no need to set the new user's supplementary groups if we are already this user
// if (!had_group && user_uid != getuid() && initgroups(user_name, user_gid) == -1) // if (!had_group && user_uid != getuid() && initgroups(user_name, user_gid) == -1)
// throw SnapException("Failed to set supplementary groups of user \"" + user + "\""); // throw SnapException("Failed to set supplementary groups of user \"" + user + "\"");
//#endif //#endif
/// set uid /// set uid
if (user_uid != (uid_t)-1 && user_uid != getuid() && setuid(user_uid) == -1) if (user_uid != (uid_t)-1 && user_uid != getuid() && setuid(user_uid) == -1)
throw SnapException("Failed to set user " + user_); throw SnapException("Failed to set user " + user_);
@ -166,6 +163,3 @@ void Daemon::daemonize()
close(STDOUT_FILENO); close(STDOUT_FILENO);
close(STDERR_FILENO); close(STDERR_FILENO);
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -62,10 +62,7 @@ protected:
writeVal(stream, payload, payloadSize); writeVal(stream, payload, payloadSize);
} }
}; };
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,9 +19,9 @@
#ifndef HELLO_MSG_H #ifndef HELLO_MSG_H
#define HELLO_MSG_H #define HELLO_MSG_H
#include "jsonMessage.h"
#include "common/utils.h"
#include "common/strCompat.h" #include "common/strCompat.h"
#include "common/utils.h"
#include "jsonMessage.h"
#include <string> #include <string>
@ -108,10 +108,7 @@ public:
return id; return id;
} }
}; };
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,8 +19,8 @@
#ifndef JSON_MESSAGE_H #ifndef JSON_MESSAGE_H
#define JSON_MESSAGE_H #define JSON_MESSAGE_H
#include "message.h"
#include "common/json.hpp" #include "common/json.hpp"
#include "message.h"
using json = nlohmann::json; using json = nlohmann::json;
@ -61,7 +61,7 @@ protected:
writeVal(stream, msg.dump()); writeVal(stream, msg.dump());
} }
template<typename T> template <typename T>
T get(const std::string& what, const T& def) const T get(const std::string& what, const T& def) const
{ {
try try
@ -70,16 +70,13 @@ protected:
return def; return def;
return msg[what].get<T>(); return msg[what].get<T>();
} }
catch(...) catch (...)
{ {
return def; return def;
} }
} }
}; };
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,14 +19,14 @@
#ifndef MESSAGE_H #ifndef MESSAGE_H
#define MESSAGE_H #define MESSAGE_H
#include "common/endian.hpp"
#include "common/timeDefs.h"
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <streambuf> #include <streambuf>
#include <vector>
#include <sys/time.h> #include <sys/time.h>
#include "common/endian.hpp" #include <vector>
#include "common/timeDefs.h"
/* /*
template<typename CharT, typename TraitsT = std::char_traits<CharT> > template<typename CharT, typename TraitsT = std::char_traits<CharT> >
@ -74,8 +74,8 @@ struct tv
sec = t.tv_sec; sec = t.tv_sec;
usec = t.tv_usec; usec = t.tv_usec;
} }
tv(timeval tv) : sec(tv.tv_sec), usec(tv.tv_usec) {}; tv(timeval tv) : sec(tv.tv_sec), usec(tv.tv_usec){};
tv(int32_t _sec, int32_t _usec) : sec(_sec), usec(_usec) {}; tv(int32_t _sec, int32_t _usec) : sec(_sec), usec(_usec){};
int32_t sec; int32_t sec;
int32_t usec; int32_t usec;
@ -178,7 +178,7 @@ struct BaseMessage
virtual uint32_t getSize() const virtual uint32_t getSize() const
{ {
return 3*sizeof(uint16_t) + 2*sizeof(tv) + sizeof(uint32_t); return 3 * sizeof(uint16_t) + 2 * sizeof(tv) + sizeof(uint32_t);
}; };
uint16_t type; uint16_t type;
@ -191,7 +191,7 @@ struct BaseMessage
protected: protected:
void writeVal(std::ostream& stream, const bool& val) const void writeVal(std::ostream& stream, const bool& val) const
{ {
char c = val?1:0; char c = val ? 1 : 0;
writeVal(stream, c); writeVal(stream, c);
} }
@ -290,9 +290,7 @@ protected:
} }
virtual void doserialize(std::ostream& stream) const virtual void doserialize(std::ostream& stream) const {};
{
};
}; };
@ -306,9 +304,6 @@ struct SerializedMessage
BaseMessage message; BaseMessage message;
char* buffer; char* buffer;
}; };
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,10 +19,10 @@
#ifndef PCM_CHUNK_H #ifndef PCM_CHUNK_H
#define PCM_CHUNK_H #define PCM_CHUNK_H
#include <chrono> #include "common/sampleFormat.h"
#include "message.h" #include "message.h"
#include "wireChunk.h" #include "wireChunk.h"
#include "common/sampleFormat.h" #include <chrono>
namespace msg namespace msg
@ -36,17 +36,11 @@ namespace msg
class PcmChunk : public WireChunk class PcmChunk : public WireChunk
{ {
public: public:
PcmChunk(const SampleFormat& sampleFormat, size_t ms) : PcmChunk(const SampleFormat& sampleFormat, size_t ms) : WireChunk(sampleFormat.rate * sampleFormat.frameSize * ms / 1000), format(sampleFormat), idx_(0)
WireChunk(sampleFormat.rate*sampleFormat.frameSize*ms / 1000),
format(sampleFormat),
idx_(0)
{ {
} }
PcmChunk(const PcmChunk& pcmChunk) : PcmChunk(const PcmChunk& pcmChunk) : WireChunk(pcmChunk), format(pcmChunk.format), idx_(0)
WireChunk(pcmChunk),
format(pcmChunk.format),
idx_(0)
{ {
} }
@ -60,17 +54,18 @@ public:
int readFrames(void* outputBuffer, size_t frameCount) int readFrames(void* outputBuffer, size_t frameCount)
{ {
//logd << "read: " << frameCount << ", total: " << (wireChunk->length / format.frameSize) << ", idx: " << idx;// << std::endl; // logd << "read: " << frameCount << ", total: " << (wireChunk->length / format.frameSize) << ", idx: " << idx;// << std::endl;
int result = frameCount; int result = frameCount;
if (idx_ + frameCount > (payloadSize / format.frameSize)) if (idx_ + frameCount > (payloadSize / format.frameSize))
result = (payloadSize / format.frameSize) - idx_; result = (payloadSize / format.frameSize) - idx_;
//logd << ", from: " << format.frameSize*idx << ", to: " << format.frameSize*idx + format.frameSize*result; // logd << ", from: " << format.frameSize*idx << ", to: " << format.frameSize*idx + format.frameSize*result;
if (outputBuffer != NULL) if (outputBuffer != NULL)
memcpy((char*)outputBuffer, (char*)(payload) + format.frameSize*idx_, format.frameSize*result); memcpy((char*)outputBuffer, (char*)(payload) + format.frameSize * idx_, format.frameSize * result);
idx_ += result; idx_ += result;
//logd << ", new idx: " << idx << ", result: " << result << ", wireChunk->length: " << wireChunk->length << ", format.frameSize: " << format.frameSize << "\n";//std::endl; // logd << ", new idx: " << idx << ", result: " << result << ", wireChunk->length: " << wireChunk->length << ", format.frameSize: " << format.frameSize
// << "\n";//std::endl;
return result; return result;
} }
@ -89,11 +84,8 @@ public:
virtual chronos::time_point_clk start() const virtual chronos::time_point_clk start() const
{ {
return chronos::time_point_clk( return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec) +
chronos::sec(timestamp.sec) + chronos::usec((chronos::usec::rep)(1000000. * ((double)idx_ / (double)format.rate))));
chronos::usec(timestamp.usec) +
chronos::usec((chronos::usec::rep)(1000000. * ((double)idx_ / (double)format.rate)))
);
} }
inline chronos::time_point_clk end() const inline chronos::time_point_clk end() const
@ -101,13 +93,13 @@ public:
return start() + durationLeft<chronos::usec>(); return start() + durationLeft<chronos::usec>();
} }
template<typename T> template <typename T>
inline T duration() const inline T duration() const
{ {
return std::chrono::duration_cast<T>(chronos::nsec((chronos::nsec::rep)(1000000 * getFrameCount() / format.msRate()))); return std::chrono::duration_cast<T>(chronos::nsec((chronos::nsec::rep)(1000000 * getFrameCount() / format.msRate())));
} }
template<typename T> template <typename T>
inline T durationLeft() const inline T durationLeft() const
{ {
return std::chrono::duration_cast<T>(chronos::nsec((chronos::nsec::rep)(1000000 * (getFrameCount() - idx_) / format.msRate()))); return std::chrono::duration_cast<T>(chronos::nsec((chronos::nsec::rep)(1000000 * (getFrameCount() - idx_) / format.msRate())));
@ -133,9 +125,6 @@ public:
private: private:
uint32_t idx_; uint32_t idx_;
}; };
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -82,10 +82,7 @@ public:
msg["muted"] = muted; msg["muted"] = muted;
} }
}; };
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -69,10 +69,7 @@ public:
{ {
} }
}; };
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -55,10 +55,7 @@ protected:
writeVal(stream, latency.usec); writeVal(stream, latency.usec);
} }
}; };
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,14 +19,14 @@
#ifndef WIRE_CHUNK_H #ifndef WIRE_CHUNK_H
#define WIRE_CHUNK_H #define WIRE_CHUNK_H
#include "common/timeDefs.h"
#include "message.h"
#include <chrono> #include <chrono>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <streambuf> #include <streambuf>
#include <vector> #include <vector>
#include "message.h"
#include "common/timeDefs.h"
namespace msg namespace msg
@ -84,10 +84,7 @@ protected:
writeVal(stream, payload, payloadSize); writeVal(stream, payload, payloadSize);
} }
}; };
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,24 +19,23 @@
#ifndef QUEUE_H #ifndef QUEUE_H
#define QUEUE_H #define QUEUE_H
#include <deque>
#include <atomic> #include <atomic>
#include <thread>
#include <mutex>
#include <condition_variable> #include <condition_variable>
#include <deque>
#include <mutex>
#include <thread>
template <typename T> template <typename T>
class Queue class Queue
{ {
public: public:
T pop() T pop()
{ {
std::unique_lock<std::mutex> mlock(mutex_); std::unique_lock<std::mutex> mlock(mutex_);
while (queue_.empty()) while (queue_.empty())
cond_.wait(mlock); cond_.wait(mlock);
// std::lock_guard<std::mutex> lock(mutex_); // std::lock_guard<std::mutex> lock(mutex_);
auto val = queue_.front(); auto val = queue_.front();
queue_.pop_front(); queue_.pop_front();
return val; return val;
@ -148,7 +147,7 @@ public:
return (size() == 0); return (size() == 0);
} }
Queue()=default; Queue() = default;
Queue(const Queue&) = delete; // disable copying Queue(const Queue&) = delete; // disable copying
Queue& operator=(const Queue&) = delete; // disable assignment Queue& operator=(const Queue&) = delete; // disable assignment
@ -161,5 +160,3 @@ private:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,15 +16,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <vector>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <vector>
#include "sampleFormat.h"
#include "common/strCompat.h"
#include "common/utils/string_utils.h"
#include "common/utils.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/strCompat.h"
#include "common/utils.h"
#include "common/utils/string_utils.h"
#include "sampleFormat.h"
using namespace std; using namespace std;
@ -60,16 +60,13 @@ void SampleFormat::setFormat(const std::string& format)
std::vector<std::string> strs; std::vector<std::string> strs;
strs = utils::string::split(format, ':'); strs = utils::string::split(format, ':');
if (strs.size() == 3) if (strs.size() == 3)
setFormat( setFormat(cpt::stoul(strs[0]), cpt::stoul(strs[1]), cpt::stoul(strs[2]));
cpt::stoul(strs[0]),
cpt::stoul(strs[1]),
cpt::stoul(strs[2]));
} }
void SampleFormat::setFormat(uint32_t rate, uint16_t bits, uint16_t channels) void SampleFormat::setFormat(uint32_t rate, uint16_t bits, uint16_t channels)
{ {
//needs something like: // needs something like:
// 24_4 = 3 bytes, padded to 4 // 24_4 = 3 bytes, padded to 4
// 32 = 4 bytes // 32 = 4 bytes
this->rate = rate; this->rate = rate;
@ -78,8 +75,6 @@ void SampleFormat::setFormat(uint32_t rate, uint16_t bits, uint16_t channels)
sampleSize = bits / 8; sampleSize = bits / 8;
if (bits == 24) if (bits == 24)
sampleSize = 4; sampleSize = 4;
frameSize = channels*sampleSize; frameSize = channels * sampleSize;
// LOG(DEBUG) << "SampleFormat: " << rate << ":" << bits << ":" << channels << "\n"; // LOG(DEBUG) << "SampleFormat: " << rate << ":" << bits << ":" << channels << "\n";
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -31,7 +31,8 @@
* 1 frame represents 1 analog sample from all channels; here we have 2 channels, and so: * 1 frame represents 1 analog sample from all channels; here we have 2 channels, and so:
* 1 frame = (num_channels) * (1 sample in bytes) = (2 channels) * (2 bytes (16 bits) per sample) = 4 bytes (32 bits) * 1 frame = (num_channels) * (1 sample in bytes) = (2 channels) * (2 bytes (16 bits) per sample) = 4 bytes (32 bits)
* To sustain 2x 44.1 KHz analog rate - the system must be capable of data transfer rate, in Bytes/sec: * To sustain 2x 44.1 KHz analog rate - the system must be capable of data transfer rate, in Bytes/sec:
* Bps_rate = (num_channels) * (1 sample in bytes) * (analog_rate) = (1 frame) * (analog_rate) = ( 2 channels ) * (2 bytes/sample) * (44100 samples/sec) = 2*2*44100 = 176400 Bytes/sec (link to formula img) * Bps_rate = (num_channels) * (1 sample in bytes) * (analog_rate) = (1 frame) * (analog_rate) = ( 2 channels ) * (2 bytes/sample) * (44100 samples/sec) =
* 2*2*44100 = 176400 Bytes/sec (link to formula img)
*/ */
class SampleFormat class SampleFormat
{ {
@ -57,20 +58,19 @@ public:
inline double msRate() const inline double msRate() const
{ {
return (double)rate/1000.; return (double)rate / 1000.;
} }
inline double usRate() const inline double usRate() const
{ {
return (double)rate/1000000.; return (double)rate / 1000000.;
} }
inline double nsRate() const inline double nsRate() const
{ {
return (double)rate/1000000000.; return (double)rate / 1000000000.;
} }
}; };
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -27,7 +27,7 @@ extern volatile sig_atomic_t g_terminated;
void signal_handler(int sig) void signal_handler(int sig)
{ {
switch(sig) switch (sig)
{ {
case SIGHUP: case SIGHUP:
syslog(LOG_WARNING, "Received SIGHUP signal."); syslog(LOG_WARNING, "Received SIGHUP signal.");
@ -47,5 +47,3 @@ void signal_handler(int sig)
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,13 +19,15 @@
#ifndef SNAP_EXCEPTION_H #ifndef SNAP_EXCEPTION_H
#define SNAP_EXCEPTION_H #define SNAP_EXCEPTION_H
#include <cstring> // std::strlen, std::strcpy
#include <exception> #include <exception>
#include <string> #include <string>
#include <cstring> // std::strlen, std::strcpy
// text_exception uses a dynamically-allocated internal c-string for what(): // text_exception uses a dynamically-allocated internal c-string for what():
class SnapException : public std::exception { class SnapException : public std::exception
{
char* text_; char* text_;
public: public:
SnapException(const char* text) SnapException(const char* text)
{ {
@ -78,5 +80,3 @@ public:
#endif #endif

View file

@ -2,105 +2,104 @@
#define COMPAT_H #define COMPAT_H
#include <string>
#include <clocale> #include <clocale>
#include <string>
#ifdef NO_CPP11_STRING #ifdef NO_CPP11_STRING
#include <sstream>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <stdexcept>
#include <cerrno> #include <cerrno>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <sstream>
#include <stdexcept>
#endif #endif
namespace cpt namespace cpt
{ {
static struct lconv* localeconv() static struct lconv* localeconv()
{ {
#ifdef NO_CPP11_STRING #ifdef NO_CPP11_STRING
static struct lconv result; static struct lconv result;
result.decimal_point = nullptr; result.decimal_point = nullptr;
result.thousands_sep = nullptr; result.thousands_sep = nullptr;
return &result; return &result;
#else #else
return std::localeconv(); return std::localeconv();
#endif #endif
} }
template<typename T> template <typename T>
static std::string to_string(const T& t) static std::string to_string(const T& t)
{ {
#ifdef NO_CPP11_STRING #ifdef NO_CPP11_STRING
std::stringstream ss; std::stringstream ss;
ss << t; ss << t;
return ss.str(); return ss.str();
#else #else
return std::to_string(t); return std::to_string(t);
#endif #endif
} }
static long stoul(const std::string& str) static long stoul(const std::string& str)
{ {
#ifdef NO_CPP11_STRING #ifdef NO_CPP11_STRING
errno = 0; errno = 0;
char *temp; char* temp;
long val = strtol(str.c_str(), &temp, 10); long val = strtol(str.c_str(), &temp, 10);
if (temp == str.c_str() || *temp != '\0') if (temp == str.c_str() || *temp != '\0')
throw std::invalid_argument("stoi"); throw std::invalid_argument("stoi");
if (((val == LONG_MIN) || (val == LONG_MAX)) && (errno == ERANGE)) if (((val == LONG_MIN) || (val == LONG_MAX)) && (errno == ERANGE))
throw std::out_of_range("stoi"); throw std::out_of_range("stoi");
return val; return val;
#else #else
return std::stoul(str); return std::stoul(str);
#endif #endif
} }
static int stoi(const std::string& str) static int stoi(const std::string& str)
{ {
#ifdef NO_CPP11_STRING #ifdef NO_CPP11_STRING
return cpt::stoul(str); return cpt::stoul(str);
#else #else
return std::stoi(str); return std::stoi(str);
#endif #endif
} }
static double stod(const std::string& str) static double stod(const std::string& str)
{ {
#ifdef NO_CPP11_STRING #ifdef NO_CPP11_STRING
errno = 0; errno = 0;
char *temp; char* temp;
double val = strtod(str.c_str(), &temp); double val = strtod(str.c_str(), &temp);
if (temp == str.c_str() || *temp != '\0') if (temp == str.c_str() || *temp != '\0')
throw std::invalid_argument("stod"); throw std::invalid_argument("stod");
if ((val == HUGE_VAL) && (errno == ERANGE)) if ((val == HUGE_VAL) && (errno == ERANGE))
throw std::out_of_range("stod"); throw std::out_of_range("stod");
return val; return val;
#else #else
return std::stod(str.c_str()); return std::stod(str.c_str());
#endif #endif
} }
static long double strtold(const char* str, char** endptr) static long double strtold(const char* str, char** endptr)
{ {
#ifdef NO_CPP11_STRING #ifdef NO_CPP11_STRING
return cpt::stod(str); return cpt::stod(str);
#else #else
return std::strtold(str, endptr); return std::strtold(str, endptr);
#endif #endif
} }
static float strtof(const char* str, char** endptr) static float strtof(const char* str, char** endptr)
{ {
#ifdef NO_CPP11_STRING #ifdef NO_CPP11_STRING
return (float)cpt::stod(str); return (float)cpt::stod(str);
#else #else
return std::strtof(str, endptr); return std::strtof(str, endptr);
#endif #endif
} }
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -20,8 +20,8 @@
#define TIME_DEFS_H #define TIME_DEFS_H
#include <chrono> #include <chrono>
#include <thread>
#include <sys/time.h> #include <sys/time.h>
#include <thread>
#ifdef MACOS #ifdef MACOS
#include <mach/clock.h> #include <mach/clock.h>
#include <mach/mach.h> #include <mach/mach.h>
@ -29,30 +29,30 @@
namespace chronos namespace chronos
{ {
typedef std::chrono::system_clock clk; typedef std::chrono::system_clock clk;
typedef std::chrono::time_point<clk> time_point_clk; typedef std::chrono::time_point<clk> time_point_clk;
typedef std::chrono::seconds sec; typedef std::chrono::seconds sec;
typedef std::chrono::milliseconds msec; typedef std::chrono::milliseconds msec;
typedef std::chrono::microseconds usec; typedef std::chrono::microseconds usec;
typedef std::chrono::nanoseconds nsec; typedef std::chrono::nanoseconds nsec;
template <class Clock> template <class Clock>
inline static void timeofday(struct timeval *tv) inline static void timeofday(struct timeval* tv)
{ {
auto now = Clock::now(); auto now = Clock::now();
auto millisecs = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()); auto millisecs = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
tv->tv_sec = millisecs.count()/1000; tv->tv_sec = millisecs.count() / 1000;
tv->tv_usec = (millisecs.count()%1000)*1000; tv->tv_usec = (millisecs.count() % 1000) * 1000;
} }
inline static void systemtimeofday(struct timeval *tv) inline static void systemtimeofday(struct timeval* tv)
{ {
gettimeofday(tv, NULL); gettimeofday(tv, NULL);
//timeofday<std::chrono::system_clock>(tv); // timeofday<std::chrono::system_clock>(tv);
} }
inline static void addUs(timeval& tv, int us) inline static void addUs(timeval& tv, int us)
{ {
if (us < 0) if (us < 0)
{ {
timeval t; timeval t;
@ -64,60 +64,58 @@ namespace chronos
tv.tv_usec += us; tv.tv_usec += us;
tv.tv_sec += (tv.tv_usec / 1000000); tv.tv_sec += (tv.tv_usec / 1000000);
tv.tv_usec %= 1000000; tv.tv_usec %= 1000000;
} }
inline static long getTickCount() inline static long getTickCount()
{ {
#ifdef MACOS #ifdef MACOS
clock_serv_t cclock; clock_serv_t cclock;
mach_timespec_t mts; mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts); clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock); mach_port_deallocate(mach_task_self(), cclock);
return mts.tv_sec*1000 + mts.tv_nsec / 1000000; return mts.tv_sec * 1000 + mts.tv_nsec / 1000000;
#else #else
struct timespec now; struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now); clock_gettime(CLOCK_MONOTONIC, &now);
return now.tv_sec*1000 + now.tv_nsec / 1000000; return now.tv_sec * 1000 + now.tv_nsec / 1000000;
#endif #endif
} }
template <class Rep, class Period> template <class Rep, class Period>
inline std::chrono::duration<Rep, Period> abs(std::chrono::duration<Rep, Period> d) inline std::chrono::duration<Rep, Period> abs(std::chrono::duration<Rep, Period> d)
{ {
Rep x = d.count(); Rep x = d.count();
return std::chrono::duration<Rep, Period>(x >= 0 ? x : -x); return std::chrono::duration<Rep, Period>(x >= 0 ? x : -x);
} }
template <class ToDuration, class Rep, class Period> template <class ToDuration, class Rep, class Period>
inline int64_t duration(std::chrono::duration<Rep, Period> d) inline int64_t duration(std::chrono::duration<Rep, Period> d)
{ {
return std::chrono::duration_cast<ToDuration>(d).count(); return std::chrono::duration_cast<ToDuration>(d).count();
} }
/// some sleep functions. Just for convenience. /// some sleep functions. Just for convenience.
template< class Rep, class Period > template <class Rep, class Period>
inline void sleep(const std::chrono::duration<Rep, Period>& sleep_duration) inline void sleep(const std::chrono::duration<Rep, Period>& sleep_duration)
{ {
std::this_thread::sleep_for(sleep_duration); std::this_thread::sleep_for(sleep_duration);
} }
inline void sleep(const int32_t& milliseconds) inline void sleep(const int32_t& milliseconds)
{ {
if (milliseconds < 0) if (milliseconds < 0)
return; return;
sleep(msec(milliseconds)); sleep(msec(milliseconds));
} }
inline void usleep(const int32_t& microseconds) inline void usleep(const int32_t& microseconds)
{ {
if (microseconds < 0) if (microseconds < 0)
return; return;
sleep(usec(microseconds)); sleep(usec(microseconds));
} }
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -22,34 +22,34 @@
#include "common/strCompat.h" #include "common/strCompat.h"
#include "common/utils/string_utils.h" #include "common/utils/string_utils.h"
#include <functional>
#include <cctype> #include <cctype>
#include <locale>
#include <string>
#include <cstring>
#include <vector>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <memory>
#include <cerrno> #include <cerrno>
#include <cstring>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iomanip>
#include <iterator> #include <iterator>
#include <sys/ioctl.h> #include <locale>
#include <memory>
#include <net/if.h> #include <net/if.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/types.h> #include <sstream>
#include <string>
#include <sys/ioctl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <iomanip> #include <vector>
#ifndef FREEBSD #ifndef FREEBSD
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#endif #endif
#include <sys/utsname.h> #include <sys/utsname.h>
#ifdef MACOS #ifdef MACOS
#include <ifaddrs.h>
#include <net/if_dl.h>
#include <IOKit/IOCFPlugIn.h> #include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOTypes.h> #include <IOKit/IOTypes.h>
#include <ifaddrs.h>
#include <net/if_dl.h>
#endif #endif
#ifdef ANDROID #ifdef ANDROID
#include <sys/system_properties.h> #include <sys/system_properties.h>
@ -80,7 +80,7 @@ static std::string execGetOutput(const std::string& cmd)
static std::string getProp(const std::string& key, const std::string& def = "") static std::string getProp(const std::string& key, const std::string& def = "")
{ {
std::string result(def); std::string result(def);
char cresult[PROP_VALUE_MAX+1]; char cresult[PROP_VALUE_MAX + 1];
if (__system_property_get(key.c_str(), cresult) > 0) if (__system_property_get(key.c_str(), cresult) > 0)
result = cresult; result = cresult;
return result; return result;
@ -186,12 +186,9 @@ static std::string generateUUID()
initialized = true; initialized = true;
} }
std::stringstream ss; std::stringstream ss;
ss << std::setfill('0') << std::hex ss << std::setfill('0') << std::hex << std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff) << "-" << std::setw(4)
<< std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff) << (std::rand() % 0xffff) << "-" << std::setw(4) << (std::rand() % 0xffff) << "-" << std::setw(4) << (std::rand() % 0xffff) << "-" << std::setw(4)
<< "-" << std::setw(4) << (std::rand() % 0xffff) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff);
<< "-" << std::setw(4) << (std::rand() % 0xffff)
<< "-" << std::setw(4) << (std::rand() % 0xffff)
<< "-" << std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff);
return ss.str(); return ss.str();
} }
@ -213,9 +210,9 @@ static std::string getMacAddress(int sock)
return ""; return "";
struct ifreq* it = ifc.ifc_req; struct ifreq* it = ifc.ifc_req;
for (int i=0; i<ifc.ifc_len;) for (int i = 0; i < ifc.ifc_len;)
{ {
/// some systems have ifr_addr.sa_len and adjust the length that way, but not mine. weird */ /// some systems have ifr_addr.sa_len and adjust the length that way, but not mine. weird */
#ifdef FREEBSD #ifdef FREEBSD
size_t len = IFNAMSIZ + it->ifr_addr.sa_len; size_t len = IFNAMSIZ + it->ifr_addr.sa_len;
#else #else
@ -230,20 +227,20 @@ static std::string getMacAddress(int sock)
#ifdef MACOS #ifdef MACOS
/// Dirty Mac version /// Dirty Mac version
struct ifaddrs *ifap, *ifaptr; struct ifaddrs *ifap, *ifaptr;
unsigned char *ptr; unsigned char* ptr;
if (getifaddrs(&ifap) == 0) if (getifaddrs(&ifap) == 0)
{ {
for (ifaptr = ifap; ifaptr != NULL; ifaptr = ifaptr->ifa_next) for (ifaptr = ifap; ifaptr != NULL; ifaptr = ifaptr->ifa_next)
{ {
// std::cout << ifaptr->ifa_name << ", " << ifreq->ifr_name << "\n"; // std::cout << ifaptr->ifa_name << ", " << ifreq->ifr_name << "\n";
if (strcmp(ifaptr->ifa_name, it->ifr_name) != 0) if (strcmp(ifaptr->ifa_name, it->ifr_name) != 0)
continue; continue;
if (ifaptr->ifa_addr->sa_family == AF_LINK) if (ifaptr->ifa_addr->sa_family == AF_LINK)
{ {
ptr = (unsigned char *)LLADDR((struct sockaddr_dl *)ifaptr->ifa_addr); ptr = (unsigned char*)LLADDR((struct sockaddr_dl*)ifaptr->ifa_addr);
char mac[19]; char mac[19];
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)); sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
if (strcmp(mac, "00:00:00:00:00:00") == 0) if (strcmp(mac, "00:00:00:00:00:00") == 0)
continue; continue;
freeifaddrs(ifap); freeifaddrs(ifap);
@ -278,7 +275,9 @@ static std::string getMacAddress(int sock)
} }
} }
} }
else { /* handle error */ } else
{ /* handle error */
}
it = (struct ifreq*)((char*)it + len); it = (struct ifreq*)((char*)it + len);
i += len; i += len;
@ -289,13 +288,13 @@ static std::string getMacAddress(int sock)
char mac[19]; char mac[19];
#ifndef FREEBSD #ifndef FREEBSD
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", (unsigned char)ifr.ifr_hwaddr.sa_data[0], (unsigned char)ifr.ifr_hwaddr.sa_data[1],
(unsigned char)ifr.ifr_hwaddr.sa_data[0], (unsigned char)ifr.ifr_hwaddr.sa_data[1], (unsigned char)ifr.ifr_hwaddr.sa_data[2], (unsigned char)ifr.ifr_hwaddr.sa_data[2], (unsigned char)ifr.ifr_hwaddr.sa_data[3], (unsigned char)ifr.ifr_hwaddr.sa_data[4],
(unsigned char)ifr.ifr_hwaddr.sa_data[3], (unsigned char)ifr.ifr_hwaddr.sa_data[4], (unsigned char)ifr.ifr_hwaddr.sa_data[5]); (unsigned char)ifr.ifr_hwaddr.sa_data[5]);
#else #else
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[0], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[1],
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[0], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[1], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[2], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[2], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[3],
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[3], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[4], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[5]); (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[4], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[5]);
#endif #endif
return mac; return mac;
} }
@ -315,7 +314,7 @@ static std::string getHostId(const std::string defaultId = "")
/// About this Mac, Hardware-UUID /// About this Mac, Hardware-UUID
char buf[64]; char buf[64];
io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
CFStringRef uuidCf = (CFStringRef) IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0);
IOObjectRelease(ioRegistryRoot); IOObjectRelease(ioRegistryRoot);
if (CFStringGetCString(uuidCf, buf, 64, kCFStringEncodingMacRoman)) if (CFStringGetCString(uuidCf, buf, 64, kCFStringEncodingMacRoman))
result = buf; result = buf;
@ -324,15 +323,15 @@ static std::string getHostId(const std::string defaultId = "")
result = getProp("ro.serialno"); result = getProp("ro.serialno");
#endif #endif
//#else //#else
// // on embedded platforms it's // // on embedded platforms it's
// // - either not there // // - either not there
// // - or not unique, or changes during boot // // - or not unique, or changes during boot
// // - or changes during boot // // - or changes during boot
// std::ifstream infile("/var/lib/dbus/machine-id"); // std::ifstream infile("/var/lib/dbus/machine-id");
// if (infile.good()) // if (infile.good())
// std::getline(infile, result); // std::getline(infile, result);
//#endif //#endif
strutils::trim(result); strutils::trim(result);
if (!result.empty()) if (!result.empty())
return result; return result;
@ -343,5 +342,3 @@ static std::string getHostId(const std::string defaultId = "")
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,12 +19,12 @@
#ifndef FILE_UTILS_H #ifndef FILE_UTILS_H
#define FILE_UTILS_H #define FILE_UTILS_H
#include "string_utils.h"
#include <fstream>
#include <grp.h> #include <grp.h>
#include <pwd.h> #include <pwd.h>
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
#include <fstream>
#include "string_utils.h"
namespace utils namespace utils
@ -53,7 +53,7 @@ static void do_chown(const std::string& file_path, const std::string& user_name,
if (!user_name.empty()) if (!user_name.empty())
{ {
struct passwd *pwd = getpwnam(user_name.c_str()); struct passwd* pwd = getpwnam(user_name.c_str());
if (pwd == NULL) if (pwd == NULL)
throw std::runtime_error("Failed to get uid"); throw std::runtime_error("Failed to get uid");
uid = pwd->pw_uid; uid = pwd->pw_uid;
@ -61,7 +61,7 @@ static void do_chown(const std::string& file_path, const std::string& user_name,
if (!group_name.empty()) if (!group_name.empty())
{ {
struct group *grp = getgrnam(group_name.c_str()); struct group* grp = getgrnam(group_name.c_str());
if (grp == NULL) if (grp == NULL)
throw std::runtime_error("Failed to get gid"); throw std::runtime_error("Failed to get gid");
gid = grp->gr_gid; gid = grp->gr_gid;
@ -72,12 +72,12 @@ static void do_chown(const std::string& file_path, const std::string& user_name,
} }
static int mkdirRecursive(const char *path, mode_t mode) static int mkdirRecursive(const char* path, mode_t mode)
{ {
std::vector<std::string> pathes = utils::string::split(path, '/'); std::vector<std::string> pathes = utils::string::split(path, '/');
std::stringstream ss; std::stringstream ss;
int res = 0; int res = 0;
for (const auto& p: pathes) for (const auto& p : pathes)
{ {
if (p.empty()) if (p.empty())
continue; continue;
@ -93,4 +93,3 @@ static int mkdirRecursive(const char *path, mode_t mode)
} // namespace utils } // namespace utils
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,10 +19,10 @@
#ifndef STRING_UTILS_H #ifndef STRING_UTILS_H
#define STRING_UTILS_H #define STRING_UTILS_H
#include <stdio.h>
#include <algorithm> #include <algorithm>
#include <string>
#include <sstream> #include <sstream>
#include <stdio.h>
#include <string>
#include <vector> #include <vector>
namespace utils namespace utils
@ -31,56 +31,57 @@ namespace string
{ {
// trim from start // trim from start
static inline std::string &ltrim(std::string &s) static inline std::string& ltrim(std::string& s)
{ {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace)))); s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace))));
return s; return s;
} }
// trim from end // trim from end
static inline std::string &rtrim(std::string &s) static inline std::string& rtrim(std::string& s)
{ {
s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end()); s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
return s; return s;
} }
// trim from both ends // trim from both ends
static inline std::string &trim(std::string &s) static inline std::string& trim(std::string& s)
{ {
return ltrim(rtrim(s)); return ltrim(rtrim(s));
} }
// trim from start // trim from start
static inline std::string ltrim_copy(const std::string &s) static inline std::string ltrim_copy(const std::string& s)
{ {
std::string str(s); std::string str(s);
return ltrim(str); return ltrim(str);
} }
// trim from end // trim from end
static inline std::string rtrim_copy(const std::string &s) static inline std::string rtrim_copy(const std::string& s)
{ {
std::string str(s); std::string str(s);
return rtrim(str); return rtrim(str);
} }
// trim from both ends // trim from both ends
static inline std::string trim_copy(const std::string &s) static inline std::string trim_copy(const std::string& s)
{ {
std::string str(s); std::string str(s);
return trim(str); return trim(str);
} }
// decode %xx to char // decode %xx to char
static std::string uriDecode(const std::string& src) { static std::string uriDecode(const std::string& src)
{
std::string ret; std::string ret;
char ch; char ch;
for (size_t i=0; i<src.length(); i++) for (size_t i = 0; i < src.length(); i++)
{ {
if (int(src[i]) == 37) if (int(src[i]) == 37)
{ {
unsigned int ii; unsigned int ii;
sscanf(src.substr(i+1, 2).c_str(), "%x", &ii); sscanf(src.substr(i + 1, 2).c_str(), "%x", &ii);
ch = static_cast<char>(ii); ch = static_cast<char>(ii);
ret += ch; ret += ch;
i += 2; i += 2;
@ -95,7 +96,7 @@ static std::string uriDecode(const std::string& src) {
static std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems) static std::vector<std::string>& split(const std::string& s, char delim, std::vector<std::string>& elems)
{ {
std::stringstream ss(s); std::stringstream ss(s);
std::string item; std::string item;
@ -107,7 +108,7 @@ static std::vector<std::string> &split(const std::string &s, char delim, std::ve
} }
static std::vector<std::string> split(const std::string &s, char delim) static std::vector<std::string> split(const std::string& s, char delim)
{ {
std::vector<std::string> elems; std::vector<std::string> elems;
split(s, delim, elems); split(s, delim, elems);
@ -118,4 +119,3 @@ static std::vector<std::string> split(const std::string &s, char delim)
} // namespace utils } // namespace utils
#endif #endif

179
doc/snapcast_plain_icon.svg Normal file
View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="220mm"
height="220mm"
viewBox="0 0 779.52756 779.52755"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="snapcast_plain_icon.svg"
inkscape:export-filename="/home/adminuser/Desktop/drawing.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffc107"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="0.70710678"
inkscape:cx="379.04793"
inkscape:cy="415.07164"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-page="true"
inkscape:snap-grids="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:window-width="1440"
inkscape:window-height="847"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
borderlayer="false"
inkscape:showpageshadow="true" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-272.83465)">
<path
style="fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:33.33300018;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 500.92383 225.22852 L 386.56445 324.14062 L 278.33008 324.14062 L 278.33008 451.42773 L 384.92383 451.42773 L 500.92383 551.76758 L 500.92383 225.22852 z "
transform="translate(0,272.83462)"
id="polygon1-3" />
<path
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
id="path3353"
sodipodi:type="arc"
sodipodi:cx="389.62741"
sodipodi:cy="663.36218"
sodipodi:rx="360.297"
sodipodi:ry="360.297"
sodipodi:start="5.7595865"
sodipodi:end="0.52359878"
d="m 701.65376,483.21367 a 360.297,360.297 0 0 1 0,360.29701"
sodipodi:open="true" />
<path
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
id="path3353-6"
sodipodi:type="arc"
sodipodi:cx="389.62741"
sodipodi:cy="663.36218"
sodipodi:rx="299.98151"
sodipodi:ry="299.98151"
sodipodi:start="5.8119464"
sodipodi:end="0.4712389"
d="m 656.91289,527.17343 a 299.98151,299.98151 0 0 1 0,272.37751"
sodipodi:open="true" />
<path
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
id="path3353-6-5"
sodipodi:type="arc"
sodipodi:cx="389.62741"
sodipodi:cy="663.36218"
sodipodi:rx="240.16534"
sodipodi:ry="240.16534"
sodipodi:start="5.8643063"
sodipodi:end="0.41887902"
d="m 609.02937,565.67814 a 240.16534,240.16534 0 0 1 0,195.36809"
sodipodi:open="true" />
<path
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
id="path3353-1"
sodipodi:type="arc"
sodipodi:cx="389.62741"
sodipodi:cy="663.36218"
sodipodi:rx="360.297"
sodipodi:ry="360.297"
sodipodi:start="3.6651914"
sodipodi:end="4.712389"
d="M 77.601053,483.21369 A 360.297,360.297 0 0 1 389.62742,303.06519"
sodipodi:open="true" />
<path
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
id="path3353-6-2"
sodipodi:type="arc"
sodipodi:cx="389.62741"
sodipodi:cy="663.36218"
sodipodi:rx="299.98151"
sodipodi:ry="299.98151"
sodipodi:start="3.7175513"
sodipodi:end="4.6600291"
d="M 138.04175,499.98055 A 299.98151,299.98151 0 0 1 373.92759,363.79179"
sodipodi:open="true" />
<path
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
id="path3353-6-5-7"
sodipodi:type="arc"
sodipodi:cx="389.62741"
sodipodi:cy="663.36218"
sodipodi:rx="240.16534"
sodipodi:ry="240.16534"
sodipodi:start="3.7699112"
sodipodi:end="4.6076692"
d="M 195.32957,522.19653 A 240.16534,240.16534 0 0 1 364.52329,424.51249"
sodipodi:open="true" />
<path
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
id="path3353-1-6"
sodipodi:type="arc"
sodipodi:cx="389.62741"
sodipodi:cy="663.36218"
sodipodi:rx="360.297"
sodipodi:ry="360.297"
sodipodi:start="1.5707963"
sodipodi:end="2.6179939"
d="M 389.62742,1023.6592 A 360.297,360.297 0 0 1 77.601055,843.51067"
sodipodi:open="true" />
<path
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
id="path3353-6-2-0"
sodipodi:type="arc"
sodipodi:cx="389.62741"
sodipodi:cy="663.36218"
sodipodi:rx="299.98151"
sodipodi:ry="299.98151"
sodipodi:start="1.6231562"
sodipodi:end="2.565634"
d="M 373.92759,962.93257 A 299.98151,299.98151 0 0 1 138.04175,826.74382"
sodipodi:open="true" />
<path
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
id="path3353-6-5-7-6"
sodipodi:type="arc"
sodipodi:cx="389.62741"
sodipodi:cy="663.36218"
sodipodi:rx="240.16534"
sodipodi:ry="240.16534"
sodipodi:start="1.6755161"
sodipodi:end="2.5132741"
d="M 364.52329,902.21188 A 240.16534,240.16534 0 0 1 195.32957,804.52783"
sodipodi:open="true" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

2
externals/Makefile vendored
View file

@ -1,5 +1,5 @@
# This file is part of snapcast # This file is part of snapcast
# Copyright (C) 2014-2018 Johannes Pohl # Copyright (C) 2014-2019 Johannes Pohl
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by

View file

@ -1,5 +1,5 @@
# This file is part of snapcast # This file is part of snapcast
# Copyright (C) 2014-2018 Johannes Pohl # Copyright (C) 2014-2019 Johannes Pohl
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -17,15 +17,15 @@
***/ ***/
#include "config.h" #include "config.h"
#include <sys/types.h> #include "aixlog.hpp"
#include <sys/stat.h>
#include <fcntl.h>
#include <fstream>
#include <cerrno>
#include "common/snapException.h" #include "common/snapException.h"
#include "common/strCompat.h" #include "common/strCompat.h"
#include "common/utils/file_utils.h" #include "common/utils/file_utils.h"
#include "aixlog.hpp" #include <cerrno>
#include <fcntl.h>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
using namespace std; using namespace std;
@ -81,7 +81,7 @@ void Config::init(const std::string& root_directory, const std::string& user, co
utils::file::do_chown(dir, user, group); utils::file::do_chown(dir, user, group);
utils::file::do_chown(filename_, user, group); utils::file::do_chown(filename_, user, group);
} }
catch(const std::exception& e) catch (const std::exception& e)
{ {
SLOG(ERROR) << "Exception in chown: " << e.what() << "\n"; SLOG(ERROR) << "Exception in chown: " << e.what() << "\n";
} }
@ -101,14 +101,14 @@ void Config::init(const std::string& root_directory, const std::string& user, co
{ {
GroupPtr group = make_shared<Group>(); GroupPtr group = make_shared<Group>();
group->fromJson(*it); group->fromJson(*it);
// if (client->id.empty() || getClientInfo(client->id)) // if (client->id.empty() || getClientInfo(client->id))
// continue; // continue;
groups.push_back(group); groups.push_back(group);
} }
} }
} }
} }
catch(const std::exception& e) catch (const std::exception& e)
{ {
LOG(ERROR) << "Error reading config: " << e.what() << "\n"; LOG(ERROR) << "Error reading config: " << e.what() << "\n";
} }
@ -119,11 +119,8 @@ void Config::save()
{ {
if (filename_.empty()) if (filename_.empty())
init(); init();
std::ofstream ofs(filename_.c_str(), std::ofstream::out|std::ofstream::trunc); std::ofstream ofs(filename_.c_str(), std::ofstream::out | std::ofstream::trunc);
json clients = { json clients = {{"ConfigVersion", 2}, {"Groups", getGroups()}};
{"ConfigVersion", 2},
{"Groups", getGroups()}
};
ofs << std::setw(4) << clients; ofs << std::setw(4) << clients;
ofs.close(); ofs.close();
} }
@ -134,9 +131,9 @@ ClientInfoPtr Config::getClientInfo(const std::string& clientId) const
if (clientId.empty()) if (clientId.empty())
return nullptr; return nullptr;
for (auto group: groups) for (auto group : groups)
{ {
for (auto client: group->clients) for (auto client : group->clients)
{ {
if (client->id == clientId) if (client->id == clientId)
return client; return client;
@ -171,7 +168,7 @@ GroupPtr Config::addClientInfo(const std::string& clientId)
GroupPtr Config::getGroup(const std::string& groupId) const GroupPtr Config::getGroup(const std::string& groupId) const
{ {
for (auto group: groups) for (auto group : groups)
{ {
if (group->id == groupId) if (group->id == groupId)
return group; return group;
@ -183,9 +180,9 @@ GroupPtr Config::getGroup(const std::string& groupId) const
GroupPtr Config::getGroupFromClient(const std::string& clientId) GroupPtr Config::getGroupFromClient(const std::string& clientId)
{ {
for (auto group: groups) for (auto group : groups)
{ {
for (auto c: group->clients) for (auto c : group->clients)
{ {
if (c->id == clientId) if (c->id == clientId)
return group; return group;
@ -205,16 +202,13 @@ json Config::getServerStatus(const json& streams) const
{ {
Host host; Host host;
host.update(); host.update();
//TODO: Set MAC and IP // TODO: Set MAC and IP
Snapserver snapserver("Snapserver", VERSION); Snapserver snapserver("Snapserver", VERSION);
json serverStatus = { json serverStatus = {{"server",
{"server", { {{"host", host.toJson()}, // getHostName()},
{"host", host.toJson()},//getHostName()}, {"snapserver", snapserver.toJson()}}},
{"snapserver", snapserver.toJson()}
}},
{"groups", getGroups()}, {"groups", getGroups()},
{"streams", streams} {"streams", streams}};
};
return serverStatus; return serverStatus;
} }
@ -224,7 +218,7 @@ json Config::getServerStatus(const json& streams) const
json Config::getGroups() const json Config::getGroups() const
{ {
json result = json::array(); json result = json::array();
for (auto group: groups) for (auto group : groups)
result.push_back(group->toJson()); result.push_back(group->toJson());
return result; return result;
} }
@ -287,5 +281,3 @@ GroupPtr Config::setGroupForClient(const std::string& groupId, const std::string
return newGroup; return newGroup;
} }
*/ */

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,14 +19,14 @@
#ifndef CONFIG_H #ifndef CONFIG_H
#define CONFIG_H #define CONFIG_H
#include <string>
#include <memory> #include <memory>
#include <vector> #include <string>
#include <sys/time.h> #include <sys/time.h>
#include <vector>
#include "common/json.hpp" #include "common/json.hpp"
#include "common/utils/string_utils.h"
#include "common/utils.h" #include "common/utils.h"
#include "common/utils/string_utils.h"
namespace strutils = utils::string; namespace strutils = utils::string;
@ -39,7 +39,7 @@ typedef std::shared_ptr<ClientInfo> ClientInfoPtr;
typedef std::shared_ptr<Group> GroupPtr; typedef std::shared_ptr<Group> GroupPtr;
template<typename T> template <typename T>
T jGet(const json& j, const std::string& what, const T& def) T jGet(const json& j, const std::string& what, const T& def)
{ {
try try
@ -48,7 +48,7 @@ T jGet(const json& j, const std::string& what, const T& def)
return def; return def;
return j[what].get<T>(); return j[what].get<T>();
} }
catch(...) catch (...)
{ {
return def; return def;
} }
@ -295,7 +295,7 @@ struct Group
j["muted"] = muted; j["muted"] = muted;
json jClients = json::array(); json jClients = json::array();
for (auto client: clients) for (auto client : clients)
jClients.push_back(client->toJson()); jClients.push_back(client->toJson());
j["clients"] = jClients; j["clients"] = jClients;
return j; return j;
@ -324,7 +324,7 @@ struct Group
ClientInfoPtr getClient(const std::string& clientId) ClientInfoPtr getClient(const std::string& clientId)
{ {
for (auto client: clients) for (auto client : clients)
{ {
if (client->id == clientId) if (client->id == clientId)
return client; return client;
@ -337,19 +337,19 @@ struct Group
if (!client) if (!client)
return; return;
for (auto c: clients) for (auto c : clients)
{ {
if (c->id == client->id) if (c->id == client->id)
return; return;
} }
clients.push_back(client); clients.push_back(client);
/* sort(clients.begin(), clients.end(), /* sort(clients.begin(), clients.end(),
[](const ClientInfoPtr a, const ClientInfoPtr b) -> bool [](const ClientInfoPtr a, const ClientInfoPtr b) -> bool
{ {
return a.name > b.name; return a.name > b.name;
}); });
*/ */
} }
bool empty() const bool empty() const
@ -380,8 +380,8 @@ public:
void remove(ClientInfoPtr client); void remove(ClientInfoPtr client);
void remove(GroupPtr group, bool force = false); void remove(GroupPtr group, bool force = false);
// GroupPtr removeFromGroup(const std::string& groupId, const std::string& clientId); // GroupPtr removeFromGroup(const std::string& groupId, const std::string& clientId);
// GroupPtr setGroupForClient(const std::string& groupId, const std::string& clientId); // GroupPtr setGroupForClient(const std::string& groupId, const std::string& clientId);
GroupPtr getGroupFromClient(const std::string& clientId); GroupPtr getGroupFromClient(const std::string& clientId);
GroupPtr getGroupFromClient(ClientInfoPtr client); GroupPtr getGroupFromClient(ClientInfoPtr client);

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,13 +16,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include "jsonrpcpp.hpp"
#include "controlServer.h" #include "controlServer.h"
#include "message/time.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/utils.h"
#include "common/snapException.h" #include "common/snapException.h"
#include "common/utils.h"
#include "config.h" #include "config.h"
#include "jsonrpcpp.hpp"
#include "message/time.h"
#include <iostream> #include <iostream>
using namespace std; using namespace std;
@ -30,32 +30,28 @@ using namespace std;
using json = nlohmann::json; using json = nlohmann::json;
ControlServer::ControlServer(asio::io_service* io_service, size_t port, ControlMessageReceiver* controlMessageReceiver) : ControlServer::ControlServer(asio::io_service* io_service, size_t port, ControlMessageReceiver* controlMessageReceiver)
acceptor_v4_(nullptr), : acceptor_v4_(nullptr), acceptor_v6_(nullptr), io_service_(io_service), port_(port), controlMessageReceiver_(controlMessageReceiver)
acceptor_v6_(nullptr),
io_service_(io_service),
port_(port),
controlMessageReceiver_(controlMessageReceiver)
{ {
} }
ControlServer::~ControlServer() ControlServer::~ControlServer()
{ {
// stop(); // stop();
} }
void ControlServer::cleanup() void ControlServer::cleanup()
{ {
std::lock_guard<std::recursive_mutex> mlock(mutex_); std::lock_guard<std::recursive_mutex> mlock(mutex_);
for (auto it = sessions_.begin(); it != sessions_.end(); ) for (auto it = sessions_.begin(); it != sessions_.end();)
{ {
if (!(*it)->active()) if (!(*it)->active())
{ {
SLOG(ERROR) << "Session inactive. Removing\n"; SLOG(ERROR) << "Session inactive. Removing\n";
// don't block: remove ClientSession in a thread // don't block: remove ClientSession in a thread
auto func = [](shared_ptr<ControlSession> s)->void{s->stop();}; auto func = [](shared_ptr<ControlSession> s) -> void { s->stop(); };
std::thread t(func, *it); std::thread t(func, *it);
t.detach(); t.detach();
//(*it)->stop(); //(*it)->stop();
@ -89,7 +85,7 @@ void ControlServer::onMessageReceived(ControlSession* connection, const std::str
if (it->get() == connection) if (it->get() == connection)
{ {
/// delete in a thread to avoid deadlock /// delete in a thread to avoid deadlock
auto func = [&](std::shared_ptr<ControlSession> s)->void{sessions_.erase(s);}; auto func = [&](std::shared_ptr<ControlSession> s) -> void { sessions_.erase(s); };
std::thread t(func, *it); std::thread t(func, *it);
t.detach(); t.detach();
break; break;
@ -196,7 +192,6 @@ void ControlServer::stop()
acceptor_v6_ = nullptr; acceptor_v6_ = nullptr;
} }
std::lock_guard<std::recursive_mutex> mlock(mutex_); std::lock_guard<std::recursive_mutex> mlock(mutex_);
for (auto s: sessions_) for (auto s : sessions_)
s->stop(); s->stop();
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -20,18 +20,18 @@
#define CONTROL_SERVER_H #define CONTROL_SERVER_H
#include <asio.hpp> #include <asio.hpp>
#include <vector>
#include <thread>
#include <memory> #include <memory>
#include <mutex>
#include <set> #include <set>
#include <sstream> #include <sstream>
#include <mutex> #include <thread>
#include <vector>
#include "controlSession.h"
#include "common/queue.h" #include "common/queue.h"
#include "common/sampleFormat.h" #include "common/sampleFormat.h"
#include "message/message.h" #include "controlSession.h"
#include "message/codecHeader.h" #include "message/codecHeader.h"
#include "message/message.h"
#include "message/serverSettings.h" #include "message/serverSettings.h"
@ -62,7 +62,7 @@ private:
void startAccept(); void startAccept();
void handleAccept(socket_ptr socket); void handleAccept(socket_ptr socket);
void cleanup(); void cleanup();
// void acceptor(); // void acceptor();
mutable std::recursive_mutex mutex_; mutable std::recursive_mutex mutex_;
std::set<std::shared_ptr<ControlSession>> sessions_; std::set<std::shared_ptr<ControlSession>> sessions_;
std::shared_ptr<tcp::acceptor> acceptor_v4_; std::shared_ptr<tcp::acceptor> acceptor_v4_;
@ -77,5 +77,3 @@ private:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,18 +16,17 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <iostream>
#include <mutex>
#include "controlSession.h" #include "controlSession.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "message/pcmChunk.h" #include "message/pcmChunk.h"
#include <iostream>
#include <mutex>
using namespace std; using namespace std;
ControlSession::ControlSession(ControlMessageReceiver* receiver, std::shared_ptr<tcp::socket> socket) : ControlSession::ControlSession(ControlMessageReceiver* receiver, std::shared_ptr<tcp::socket> socket) : active_(false), messageReceiver_(receiver)
active_(false), messageReceiver_(receiver)
{ {
socket_ = socket; socket_ = socket;
} }
@ -62,9 +61,11 @@ void ControlSession::stop()
{ {
std::lock_guard<std::recursive_mutex> socketLock(socketMutex_); std::lock_guard<std::recursive_mutex> socketLock(socketMutex_);
socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec);
if (ec) LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n"; if (ec)
LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n";
socket_->close(ec); socket_->close(ec);
if (ec) LOG(ERROR) << "Error in socket close: " << ec.message() << "\n"; if (ec)
LOG(ERROR) << "Error in socket close: " << ec.message() << "\n";
} }
if (readerThread_.joinable()) if (readerThread_.joinable())
{ {
@ -78,7 +79,7 @@ void ControlSession::stop()
writerThread_.join(); writerThread_.join();
} }
} }
catch(...) catch (...)
{ {
} }
socket_ = NULL; socket_ = NULL;
@ -95,7 +96,7 @@ void ControlSession::sendAsync(const std::string& message)
bool ControlSession::send(const std::string& message) const bool ControlSession::send(const std::string& message) const
{ {
//LOG(INFO) << "send: " << message << ", size: " << message.length() << "\n"; // LOG(INFO) << "send: " << message << ", size: " << message.length() << "\n";
std::lock_guard<std::recursive_mutex> socketLock(socketMutex_); std::lock_guard<std::recursive_mutex> socketLock(socketMutex_);
{ {
std::lock_guard<std::recursive_mutex> activeLock(activeMutex_); std::lock_guard<std::recursive_mutex> activeLock(activeMutex_);
@ -106,7 +107,7 @@ bool ControlSession::send(const std::string& message) const
std::ostream request_stream(&streambuf); std::ostream request_stream(&streambuf);
request_stream << message << "\r\n"; request_stream << message << "\r\n";
asio::write(*socket_.get(), streambuf); asio::write(*socket_.get(), streambuf);
//LOG(INFO) << "done\n"; // LOG(INFO) << "done\n";
return true; return true;
} }
@ -133,7 +134,7 @@ void ControlSession::reader()
continue; continue;
size_t len = line.length() - 1; size_t len = line.length() - 1;
if ((len >= 2) && line[len-2] == '\r') if ((len >= 2) && line[len - 2] == '\r')
--len; --len;
line.resize(len); line.resize(len);
if ((messageReceiver_ != NULL) && !line.empty()) if ((messageReceiver_ != NULL) && !line.empty())
@ -170,5 +171,3 @@ void ControlSession::writer()
} }
active_ = false; active_ = false;
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,16 +19,16 @@
#ifndef CONTROL_SESSION_H #ifndef CONTROL_SESSION_H
#define CONTROL_SESSION_H #define CONTROL_SESSION_H
#include "common/queue.h"
#include "message/message.h"
#include <asio.hpp>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <set>
#include <string> #include <string>
#include <thread> #include <thread>
#include <atomic>
#include <mutex>
#include <memory>
#include <asio.hpp>
#include <condition_variable>
#include <set>
#include "message/message.h"
#include "common/queue.h"
using asio::ip::tcp; using asio::ip::tcp;
@ -87,9 +87,4 @@ protected:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,12 +19,12 @@
#ifndef ENCODER_H #ifndef ENCODER_H
#define ENCODER_H #define ENCODER_H
#include <string>
#include <memory> #include <memory>
#include <string>
#include "message/pcmChunk.h"
#include "message/codecHeader.h"
#include "common/sampleFormat.h" #include "common/sampleFormat.h"
#include "message/codecHeader.h"
#include "message/pcmChunk.h"
class Encoder; class Encoder;
@ -100,5 +100,3 @@ protected:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -24,9 +24,9 @@
#if defined(HAS_FLAC) #if defined(HAS_FLAC)
#include "flacEncoder.h" #include "flacEncoder.h"
#endif #endif
#include "common/utils/string_utils.h"
#include "common/snapException.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/snapException.h"
#include "common/utils/string_utils.h"
using namespace std; using namespace std;
@ -58,7 +58,7 @@ Encoder* EncoderFactory::createEncoder(const std::string& codecSettings) const
} }
return encoder; return encoder;
/* try /* try
{ {
encoder->init(NULL, format, codecOptions); encoder->init(NULL, format, codecOptions);
} }
@ -67,8 +67,5 @@ Encoder* EncoderFactory::createEncoder(const std::string& codecSettings) const
cout << "Error: " << e.what() << "\n"; cout << "Error: " << e.what() << "\n";
return 1; return 1;
} }
*/ */
} }

View file

@ -1,13 +1,13 @@
#ifndef ENCODER_FACTORY_H #ifndef ENCODER_FACTORY_H
#define ENCODER_FACTORY_H #define ENCODER_FACTORY_H
#include <string>
#include "encoder.h" #include "encoder.h"
#include <string>
class EncoderFactory class EncoderFactory
{ {
public: public:
// EncoderFactory(const std::string& codecSettings); // EncoderFactory(const std::string& codecSettings);
Encoder* createEncoder(const std::string& codecSettings) const; Encoder* createEncoder(const std::string& codecSettings) const;
}; };

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -18,10 +18,10 @@
#include <iostream> #include <iostream>
#include "flacEncoder.h"
#include "common/strCompat.h"
#include "common/snapException.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/snapException.h"
#include "common/strCompat.h"
#include "flacEncoder.h"
using namespace std; using namespace std;
@ -71,7 +71,8 @@ void FlacEncoder::encode(const msg::PcmChunk* chunk)
{ {
int samples = chunk->getSampleCount(); int samples = chunk->getSampleCount();
int frames = chunk->getFrameCount(); int frames = chunk->getFrameCount();
// LOG(INFO) << "payload: " << chunk->payloadSize << "\tframes: " << frames << "\tsamples: " << samples << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n"; // LOG(INFO) << "payload: " << chunk->payloadSize << "\tframes: " << frames << "\tsamples: " << samples << "\tduration: " <<
//chunk->duration<chronos::msec>().count() << "\n";
if (pcmBufferSize_ < samples) if (pcmBufferSize_ < samples)
{ {
@ -82,19 +83,19 @@ void FlacEncoder::encode(const msg::PcmChunk* chunk)
if (sampleFormat_.sampleSize == 1) if (sampleFormat_.sampleSize == 1)
{ {
FLAC__int8* buffer = (FLAC__int8*)chunk->payload; FLAC__int8* buffer = (FLAC__int8*)chunk->payload;
for(int i=0; i<samples; i++) for (int i = 0; i < samples; i++)
pcmBuffer_[i] = (FLAC__int32)(buffer[i]); pcmBuffer_[i] = (FLAC__int32)(buffer[i]);
} }
else if (sampleFormat_.sampleSize == 2) else if (sampleFormat_.sampleSize == 2)
{ {
FLAC__int16* buffer = (FLAC__int16*)chunk->payload; FLAC__int16* buffer = (FLAC__int16*)chunk->payload;
for(int i=0; i<samples; i++) for (int i = 0; i < samples; i++)
pcmBuffer_[i] = (FLAC__int32)(buffer[i]); pcmBuffer_[i] = (FLAC__int32)(buffer[i]);
} }
else if (sampleFormat_.sampleSize == 4) else if (sampleFormat_.sampleSize == 4)
{ {
FLAC__int32* buffer = (FLAC__int32*)chunk->payload; FLAC__int32* buffer = (FLAC__int32*)chunk->payload;
for(int i=0; i<samples; i++) for (int i = 0; i < samples; i++)
pcmBuffer_[i] = (FLAC__int32)(buffer[i]); pcmBuffer_[i] = (FLAC__int32)(buffer[i]);
} }
@ -104,7 +105,7 @@ void FlacEncoder::encode(const msg::PcmChunk* chunk)
if (encodedSamples_ > 0) if (encodedSamples_ > 0)
{ {
double resMs = encodedSamples_ / ((double)sampleFormat_.rate / 1000.); double resMs = encodedSamples_ / ((double)sampleFormat_.rate / 1000.);
// LOG(INFO) << "encoded: " << chunk->payloadSize << "\tframes: " << encodedSamples_ << "\tres: " << resMs << "\n"; // LOG(INFO) << "encoded: " << chunk->payloadSize << "\tframes: " << encodedSamples_ << "\tres: " << resMs << "\n";
encodedSamples_ = 0; encodedSamples_ = 0;
listener_->onChunkEncoded(this, flacChunk_, resMs); listener_->onChunkEncoded(this, flacChunk_, resMs);
flacChunk_ = new msg::PcmChunk(chunk->format, 0); flacChunk_ = new msg::PcmChunk(chunk->format, 0);
@ -112,13 +113,10 @@ void FlacEncoder::encode(const msg::PcmChunk* chunk)
} }
FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEncoder *encoder, FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples,
const FLAC__byte buffer[],
size_t bytes,
unsigned samples,
unsigned current_frame) unsigned current_frame)
{ {
// LOG(INFO) << "write_callback: " << bytes << ", " << samples << ", " << current_frame << "\n"; // LOG(INFO) << "write_callback: " << bytes << ", " << samples << ", " << current_frame << "\n";
if ((current_frame == 0) && (bytes > 0) && (samples == 0)) if ((current_frame == 0) && (bytes > 0) && (samples == 0))
{ {
headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize + bytes); headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize + bytes);
@ -136,12 +134,8 @@ FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEnc
} }
FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder *encoder, FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples,
const FLAC__byte buffer[], unsigned current_frame, void* client_data)
size_t bytes,
unsigned samples,
unsigned current_frame,
void *client_data)
{ {
FlacEncoder* flacEncoder = (FlacEncoder*)client_data; FlacEncoder* flacEncoder = (FlacEncoder*)client_data;
return flacEncoder->write_callback(encoder, buffer, bytes, samples, current_frame); return flacEncoder->write_callback(encoder, buffer, bytes, samples, current_frame);
@ -155,7 +149,7 @@ void FlacEncoder::initEncoder()
{ {
quality = cpt::stoi(codecOptions_); quality = cpt::stoi(codecOptions_);
} }
catch(...) catch (...)
{ {
throw SnapException("Invalid codec option: \"" + codecOptions_ + "\""); throw SnapException("Invalid codec option: \"" + codecOptions_ + "\"");
} }
@ -187,15 +181,13 @@ void FlacEncoder::initEncoder()
throw SnapException("error setting up encoder"); throw SnapException("error setting up encoder");
// now add some metadata; we'll add some tags and a padding block // now add some metadata; we'll add some tags and a padding block
if ( if ((metadata_[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == NULL ||
(metadata_[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == NULL ||
(metadata_[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING)) == NULL || (metadata_[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING)) == NULL ||
// there are many tag (vorbiscomment) functions but these are convenient for this particular use: // there are many tag (vorbiscomment) functions but these are convenient for this particular use:
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "TITLE", "SnapStream") || !FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "TITLE", "SnapStream") ||
!FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false) || !FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false) ||
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "VERSION", VERSION) || !FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "VERSION", VERSION) ||
!FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false) !FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false))
)
throw SnapException("out of memory or tag error"); throw SnapException("out of memory or tag error");
metadata_[1]->length = 1234; // set the padding length metadata_[1]->length = 1234; // set the padding length
@ -205,7 +197,6 @@ void FlacEncoder::initEncoder()
// initialize encoder // initialize encoder
init_status = FLAC__stream_encoder_init_stream(encoder_, ::write_callback, NULL, NULL, NULL, this); init_status = FLAC__stream_encoder_init_stream(encoder_, ::write_callback, NULL, NULL, NULL, this);
if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
throw SnapException("ERROR: initializing encoder: " + string(FLAC__StreamEncoderInitStatusString[init_status])); throw SnapException("ERROR: initializing encoder: " + string(FLAC__StreamEncoderInitStatusString[init_status]));
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -37,15 +37,16 @@ public:
virtual std::string getDefaultOptions() const; virtual std::string getDefaultOptions() const;
virtual std::string name() const; virtual std::string name() const;
FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame); FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples,
unsigned current_frame);
protected: protected:
virtual void initEncoder(); virtual void initEncoder();
FLAC__StreamEncoder *encoder_; FLAC__StreamEncoder* encoder_;
FLAC__StreamMetadata *metadata_[2]; FLAC__StreamMetadata* metadata_[2];
FLAC__int32 *pcmBuffer_; FLAC__int32* pcmBuffer_;
int pcmBufferSize_; int pcmBufferSize_;
msg::PcmChunk* flacChunk_; msg::PcmChunk* flacChunk_;
@ -54,5 +55,3 @@ protected:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,15 +16,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <iostream>
#include <cstring> #include <cstring>
#include <iostream>
#include "oggEncoder.h" #include "aixlog.hpp"
#include "common/snapException.h" #include "common/snapException.h"
#include "common/strCompat.h" #include "common/strCompat.h"
#include "common/utils/string_utils.h"
#include "common/utils.h" #include "common/utils.h"
#include "aixlog.hpp" #include "common/utils/string_utils.h"
#include "oggEncoder.h"
using namespace std; using namespace std;
@ -55,9 +55,10 @@ std::string OggEncoder::name() const
void OggEncoder::encode(const msg::PcmChunk* chunk) void OggEncoder::encode(const msg::PcmChunk* chunk)
{ {
double res = 0; double res = 0;
LOG(DEBUG) << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n"; LOG(DEBUG) << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count()
<< "\n";
int frames = chunk->getFrameCount(); int frames = chunk->getFrameCount();
float **buffer=vorbis_analysis_buffer(&vd_, frames); float** buffer = vorbis_analysis_buffer(&vd_, frames);
/* uninterleave samples */ /* uninterleave samples */
for (size_t channel = 0; channel < sampleFormat_.channels; ++channel) for (size_t channel = 0; channel < sampleFormat_.channels; ++channel)
@ -65,20 +66,20 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
if (sampleFormat_.sampleSize == 1) if (sampleFormat_.sampleSize == 1)
{ {
int8_t* chunkBuffer = (int8_t*)chunk->payload; int8_t* chunkBuffer = (int8_t*)chunk->payload;
for (int i=0; i<frames; i++) for (int i = 0; i < frames; i++)
buffer[channel][i]= chunkBuffer[sampleFormat_.channels*i + channel] / 128.f; buffer[channel][i] = chunkBuffer[sampleFormat_.channels * i + channel] / 128.f;
} }
else if (sampleFormat_.sampleSize == 2) else if (sampleFormat_.sampleSize == 2)
{ {
int16_t* chunkBuffer = (int16_t*)chunk->payload; int16_t* chunkBuffer = (int16_t*)chunk->payload;
for (int i=0; i<frames; i++) for (int i = 0; i < frames; i++)
buffer[channel][i]= chunkBuffer[sampleFormat_.channels*i + channel] / 32768.f; buffer[channel][i] = chunkBuffer[sampleFormat_.channels * i + channel] / 32768.f;
} }
else if (sampleFormat_.sampleSize == 4) else if (sampleFormat_.sampleSize == 4)
{ {
int32_t* chunkBuffer = (int32_t*)chunk->payload; int32_t* chunkBuffer = (int32_t*)chunk->payload;
for (int i=0; i<frames; i++) for (int i = 0; i < frames; i++)
buffer[channel][i]= chunkBuffer[sampleFormat_.channels*i + channel] / 2147483648.f; buffer[channel][i] = chunkBuffer[sampleFormat_.channels * i + channel] / 2147483648.f;
} }
} }
@ -91,7 +92,7 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
more involved (potentially parallel) processing. Get a single more involved (potentially parallel) processing. Get a single
block for encoding now */ block for encoding now */
size_t pos = 0; size_t pos = 0;
while (vorbis_analysis_blockout(&vd_, &vb_)==1) while (vorbis_analysis_blockout(&vd_, &vb_) == 1)
{ {
/* analysis, assume we want to use bitrate management */ /* analysis, assume we want to use bitrate management */
vorbis_analysis(&vb_, NULL); vorbis_analysis(&vb_, NULL);
@ -155,7 +156,7 @@ void OggEncoder::initEncoder()
{ {
quality = cpt::stod(qual); quality = cpt::stod(qual);
} }
catch(...) catch (...)
{ {
throw SnapException("Invalid codec option: \"" + codecOptions_ + "\""); throw SnapException("Invalid codec option: \"" + codecOptions_ + "\"");
} }
@ -256,5 +257,3 @@ void OggEncoder::initEncoder()
pos += og_.body_len; pos += og_.body_len;
} }
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,8 +19,8 @@
#ifndef OGG_ENCODER_H #ifndef OGG_ENCODER_H
#define OGG_ENCODER_H #define OGG_ENCODER_H
#include "encoder.h" #include "encoder.h"
#include <vorbis/vorbisenc.h>
#include <ogg/ogg.h> #include <ogg/ogg.h>
#include <vorbis/vorbisenc.h>
class OggEncoder : public Encoder class OggEncoder : public Encoder
{ {
@ -50,5 +50,3 @@ private:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,9 +16,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <memory>
#include "common/endian.hpp"
#include "pcmEncoder.h" #include "pcmEncoder.h"
#include "common/endian.hpp"
#include <memory>
#define ID_RIFF 0x46464952 #define ID_RIFF 0x46464952
@ -65,5 +65,3 @@ std::string PcmEncoder::name() const
{ {
return "pcm"; return "pcm";
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -31,7 +31,7 @@ public:
protected: protected:
virtual void initEncoder(); virtual void initEncoder();
template<typename T> template <typename T>
void assign(void* pointer, T val) void assign(void* pointer, T val)
{ {
T* p = (T*)pointer; T* p = (T*)pointer;
@ -41,5 +41,3 @@ protected:
#endif #endif

View file

@ -17,18 +17,17 @@
USA. USA.
***/ ***/
#include <cstdio>
#include <cstdlib>
#include "publishAvahi.h" #include "publishAvahi.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include <cstdio>
#include <cstdlib>
static AvahiEntryGroup *group; static AvahiEntryGroup* group;
static AvahiSimplePoll *simple_poll; static AvahiSimplePoll* simple_poll;
static char* name; static char* name;
PublishAvahi::PublishAvahi(const std::string& serviceName) : PublishmDNS(serviceName), PublishAvahi::PublishAvahi(const std::string& serviceName) : PublishmDNS(serviceName), client_(NULL), active_(false)
client_(NULL), active_(false)
{ {
group = NULL; group = NULL;
simple_poll = NULL; simple_poll = NULL;
@ -43,7 +42,7 @@ void PublishAvahi::publish(const std::vector<mDNSService>& services)
/// Allocate main loop object /// Allocate main loop object
if (!(simple_poll = avahi_simple_poll_new())) if (!(simple_poll = avahi_simple_poll_new()))
{ {
///TODO: error handling /// TODO: error handling
LOG(ERROR) << "Failed to create simple poll object.\n"; LOG(ERROR) << "Failed to create simple poll object.\n";
} }
@ -64,7 +63,8 @@ void PublishAvahi::publish(const std::vector<mDNSService>& services)
void PublishAvahi::worker() void PublishAvahi::worker()
{ {
while (active_ && (avahi_simple_poll_iterate(simple_poll, 100) == 0)); while (active_ && (avahi_simple_poll_iterate(simple_poll, 100) == 0))
;
} }
@ -83,7 +83,7 @@ PublishAvahi::~PublishAvahi()
} }
void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) void PublishAvahi::entry_group_callback(AvahiEntryGroup* g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void* userdata)
{ {
assert(g == group || group == NULL); assert(g == group || group == NULL);
group = g; group = g;
@ -91,14 +91,14 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
/// Called whenever the entry group state changes /// Called whenever the entry group state changes
switch (state) switch (state)
{ {
case AVAHI_ENTRY_GROUP_ESTABLISHED : case AVAHI_ENTRY_GROUP_ESTABLISHED:
/// The entry group has been established successfully /// The entry group has been established successfully
LOG(INFO) << "Service '" << name << "' successfully established.\n"; LOG(INFO) << "Service '" << name << "' successfully established.\n";
break; break;
case AVAHI_ENTRY_GROUP_COLLISION : case AVAHI_ENTRY_GROUP_COLLISION:
{ {
char *n; char* n;
/// A service name collision with a remote service happened. Let's pick a new name /// A service name collision with a remote service happened. Let's pick a new name
n = avahi_alternative_service_name(name); n = avahi_alternative_service_name(name);
@ -112,7 +112,7 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
break; break;
} }
case AVAHI_ENTRY_GROUP_FAILURE : case AVAHI_ENTRY_GROUP_FAILURE:
LOG(ERROR) << "Entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) << "\n"; LOG(ERROR) << "Entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) << "\n";
@ -121,15 +121,14 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
break; break;
case AVAHI_ENTRY_GROUP_UNCOMMITED: case AVAHI_ENTRY_GROUP_UNCOMMITED:
case AVAHI_ENTRY_GROUP_REGISTERING: case AVAHI_ENTRY_GROUP_REGISTERING:;
;
} }
} }
void PublishAvahi::create_services(AvahiClient *c) void PublishAvahi::create_services(AvahiClient* c)
{ {
assert(c); assert(c);
char *n; char* n;
/// If this is the first time we're called, let's create a new entry group if necessary /// If this is the first time we're called, let's create a new entry group if necessary
if (!group) if (!group)
@ -148,9 +147,10 @@ void PublishAvahi::create_services(AvahiClient *c)
LOG(INFO) << "Adding service '" << name << "'\n"; LOG(INFO) << "Adding service '" << name << "'\n";
/// We will now add two services and one subtype to the entry group /// We will now add two services and one subtype to the entry group
for (const auto& service: services_) for (const auto& service : services_)
{ {
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, service.name_.c_str(), NULL, NULL, service.port_, NULL)) < 0) if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, service.name_.c_str(), NULL, NULL,
service.port_, NULL)) < 0)
{ {
if (ret == AVAHI_ERR_COLLISION) if (ret == AVAHI_ERR_COLLISION)
goto collision; goto collision;
@ -161,12 +161,13 @@ void PublishAvahi::create_services(AvahiClient *c)
} }
/// Add an additional (hypothetic) subtype /// Add an additional (hypothetic) subtype
/* if ((ret = avahi_entry_group_add_service_subtype(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, "_printer._tcp", NULL, "_magic._sub._printer._tcp") < 0)) /* if ((ret = avahi_entry_group_add_service_subtype(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, "_printer._tcp",
NULL, "_magic._sub._printer._tcp") < 0))
{ {
fprintf(stderr, "Failed to add subtype _magic._sub._printer._tcp: %s\n", avahi_strerror(ret)); fprintf(stderr, "Failed to add subtype _magic._sub._printer._tcp: %s\n", avahi_strerror(ret));
goto fail; goto fail;
} }
*/ */
/// Tell the server to register the service /// Tell the server to register the service
if ((ret = avahi_entry_group_commit(group)) < 0) if ((ret = avahi_entry_group_commit(group)) < 0)
{ {
@ -196,7 +197,7 @@ fail:
} }
void PublishAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) void PublishAvahi::client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void* userdata)
{ {
assert(c); assert(c);
@ -229,10 +230,6 @@ void PublishAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI
avahi_entry_group_reset(group); avahi_entry_group_reset(group);
break; break;
case AVAHI_CLIENT_CONNECTING: case AVAHI_CLIENT_CONNECTING:;
;
} }
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -23,15 +23,15 @@
#include <avahi-client/client.h> #include <avahi-client/client.h>
#include <avahi-client/publish.h> #include <avahi-client/publish.h>
#include <atomic>
#include <avahi-common/alternative.h> #include <avahi-common/alternative.h>
#include <avahi-common/simple-watch.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h> #include <avahi-common/error.h>
#include <avahi-common/malloc.h>
#include <avahi-common/simple-watch.h>
#include <avahi-common/timeval.h> #include <avahi-common/timeval.h>
#include <string> #include <string>
#include <vector>
#include <thread> #include <thread>
#include <atomic> #include <vector>
class PublishAvahi; class PublishAvahi;
@ -45,9 +45,9 @@ public:
virtual void publish(const std::vector<mDNSService>& services); virtual void publish(const std::vector<mDNSService>& services);
private: private:
static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata); static void entry_group_callback(AvahiEntryGroup* g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void* userdata);
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata); static void client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void* userdata);
void create_services(AvahiClient *c); void create_services(AvahiClient* c);
void worker(); void worker();
AvahiClient* client_; AvahiClient* client_;
std::thread pollThread_; std::thread pollThread_;
@ -57,5 +57,3 @@ private:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,16 +19,19 @@
#include <cstdlib> #include <cstdlib>
#include <thread> #include <thread>
#include "publishBonjour.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "publishBonjour.h"
typedef union { unsigned char b[2]; unsigned short NotAnInteger; } Opaque16; typedef union {
unsigned char b[2];
unsigned short NotAnInteger;
} Opaque16;
PublishBonjour::PublishBonjour(const std::string& serviceName) : PublishmDNS(serviceName), active_(false) PublishBonjour::PublishBonjour(const std::string& serviceName) : PublishmDNS(serviceName), active_(false)
{ {
/// dns-sd -R Snapcast _snapcast._tcp local 1704 /// dns-sd -R Snapcast _snapcast._tcp local 1704
/// dns-sd -R Snapcast _snapcast-jsonrpc._tcp local 1705 /// dns-sd -R Snapcast _snapcast-jsonrpc._tcp local 1705
} }
@ -36,7 +39,7 @@ PublishBonjour::~PublishBonjour()
{ {
active_ = false; active_ = false;
pollThread_.join(); pollThread_.join();
for (auto client: clients) for (auto client : clients)
{ {
if (client) if (client)
DNSServiceRefDeallocate(client); DNSServiceRefDeallocate(client);
@ -47,7 +50,7 @@ PublishBonjour::~PublishBonjour()
void PublishBonjour::worker() void PublishBonjour::worker()
{ {
// int dns_sd_fd = client ? DNSServiceRefSockFD(client) : -1; // int dns_sd_fd = client ? DNSServiceRefSockFD(client) : -1;
// 1. Set up the fd_set as usual here. // 1. Set up the fd_set as usual here.
// This example client has no file descriptors of its own, // This example client has no file descriptors of its own,
// but a real application would call FD_SET to add them to the set here // but a real application would call FD_SET to add them to the set here
@ -56,7 +59,7 @@ void PublishBonjour::worker()
std::vector<int> dns_sd_fds; std::vector<int> dns_sd_fds;
int nfds = -1; int nfds = -1;
for (size_t n=0; n<clients.size(); ++n) for (size_t n = 0; n < clients.size(); ++n)
{ {
int dns_sd_fd = DNSServiceRefSockFD(clients[n]); int dns_sd_fd = DNSServiceRefSockFD(clients[n]);
dns_sd_fds.push_back(dns_sd_fd); dns_sd_fds.push_back(dns_sd_fd);
@ -70,20 +73,20 @@ void PublishBonjour::worker()
// 3. Set up the timeout. // 3. Set up the timeout.
struct timeval tv; struct timeval tv;
tv.tv_sec = 0; tv.tv_sec = 0;
tv.tv_usec = 100*1000; tv.tv_usec = 100 * 1000;
active_ = true; active_ = true;
while (active_) while (active_)
{ {
FD_ZERO(&readfds); FD_ZERO(&readfds);
for (size_t n=0; n<dns_sd_fds.size(); ++n) for (size_t n = 0; n < dns_sd_fds.size(); ++n)
FD_SET(dns_sd_fds[n], &readfds); FD_SET(dns_sd_fds[n], &readfds);
int result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv); int result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
if (result > 0) if (result > 0)
{ {
for (size_t n=0; n<dns_sd_fds.size(); ++n) for (size_t n = 0; n < dns_sd_fds.size(); ++n)
{ {
if (clients[n] && FD_ISSET(dns_sd_fds[n], &readfds)) if (clients[n] && FD_ISSET(dns_sd_fds[n], &readfds))
{ {
@ -96,8 +99,8 @@ void PublishBonjour::worker()
} }
} }
} }
// else if (result == 0) // else if (result == 0)
// myTimerCallBack(); // myTimerCallBack();
else if (result < 0) else if (result < 0)
{ {
LOG(ERROR) << "select() returned " << result << " errno " << errno << " " << strerror(errno) << "\n"; LOG(ERROR) << "select() returned " << result << " errno " << errno << " " << strerror(errno) << "\n";
@ -108,7 +111,8 @@ void PublishBonjour::worker()
} }
static void DNSSD_API reg_reply(DNSServiceRef sdref, const DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context) static void DNSSD_API reg_reply(DNSServiceRef sdref, const DNSServiceFlags flags, DNSServiceErrorType errorCode, const char* name, const char* regtype,
const char* domain, void* context)
{ {
(void)sdref; // Unused (void)sdref; // Unused
(void)flags; // Unused (void)flags; // Unused
@ -141,19 +145,17 @@ static void DNSSD_API reg_reply(DNSServiceRef sdref, const DNSServiceFlags flags
void PublishBonjour::publish(const std::vector<mDNSService>& services) void PublishBonjour::publish(const std::vector<mDNSService>& services)
{ {
for (auto service: services) for (auto service : services)
{ {
DNSServiceFlags flags = 0; DNSServiceFlags flags = 0;
Opaque16 registerPort = { { static_cast<unsigned char>(service.port_ >> 8), static_cast<unsigned char>(service.port_ & 0xFF) } }; Opaque16 registerPort = {{static_cast<unsigned char>(service.port_ >> 8), static_cast<unsigned char>(service.port_ & 0xFF)}};
DNSServiceRef client = NULL; DNSServiceRef client = NULL;
// DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL, registerPort.NotAnInteger, service.txt_.size(), service.txt_.empty()?NULL:service.txt_.c_str(), reg_reply, this); // DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL,
DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL, registerPort.NotAnInteger, 0, NULL, reg_reply, this); //registerPort.NotAnInteger, service.txt_.size(), service.txt_.empty()?NULL:service.txt_.c_str(), reg_reply, this);
DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL, registerPort.NotAnInteger, 0,
NULL, reg_reply, this);
clients.push_back(client); clients.push_back(client);
} }
pollThread_ = std::thread(&PublishBonjour::worker, this); pollThread_ = std::thread(&PublishBonjour::worker, this);
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -20,8 +20,8 @@
#ifndef PUBLISH_BONJOUR_H #ifndef PUBLISH_BONJOUR_H
#define PUBLISH_BONJOUR_H #define PUBLISH_BONJOUR_H
#include <string>
#include <dns_sd.h> #include <dns_sd.h>
#include <string>
class PublishBonjour; class PublishBonjour;
@ -43,5 +43,3 @@ private:
#endif #endif

0
server/publishZeroConf/publishmDNS.h Executable file → Normal file
View file

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -24,19 +24,19 @@
#ifdef HAS_DAEMON #ifdef HAS_DAEMON
#include "common/daemon.h" #include "common/daemon.h"
#endif #endif
#include "common/timeDefs.h" #include "common/sampleFormat.h"
#include "common/utils/string_utils.h"
#include "common/signalHandler.h" #include "common/signalHandler.h"
#include "common/snapException.h" #include "common/snapException.h"
#include "common/sampleFormat.h" #include "common/timeDefs.h"
#include "message/message.h" #include "common/utils/string_utils.h"
#include "encoder/encoderFactory.h" #include "encoder/encoderFactory.h"
#include "message/message.h"
#include "streamServer.h" #include "streamServer.h"
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR) #if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
#include "publishZeroConf/publishmDNS.h" #include "publishZeroConf/publishmDNS.h"
#endif #endif
#include "config.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "config.h"
volatile sig_atomic_t g_terminated = false; volatile sig_atomic_t g_terminated = false;
@ -63,15 +63,18 @@ int main(int argc, char* argv[])
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message"); auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message");
auto debugOption = op.add<Implicit<string>, Attribute::hidden>("", "debug", "enable debug logging", ""); auto debugOption = op.add<Implicit<string>, Attribute::hidden>("", "debug", "enable debug logging", "");
auto versionSwitch = op.add<Switch>("v", "version", "Show version number"); auto versionSwitch = op.add<Switch>("v", "version", "Show version number");
/*auto portValue =*/ op.add<Value<size_t>>("p", "port", "Server port", settings.port, &settings.port); /*auto portValue =*/op.add<Value<size_t>>("p", "port", "Server port", settings.port, &settings.port);
/*auto controlPortValue =*/ op.add<Value<size_t>>("", "controlPort", "Remote control port", settings.controlPort, &settings.controlPort); /*auto controlPortValue =*/op.add<Value<size_t>>("", "controlPort", "Remote control port", settings.controlPort, &settings.controlPort);
auto streamValue = op.add<Value<string>>("s", "stream", "URI of the PCM input stream.\nFormat: TYPE://host/path?name=NAME\n[&codec=CODEC]\n[&sampleformat=SAMPLEFORMAT]", pcmStream, &pcmStream); auto streamValue = op.add<Value<string>>(
"s", "stream", "URI of the PCM input stream.\nFormat: TYPE://host/path?name=NAME\n[&codec=CODEC]\n[&sampleformat=SAMPLEFORMAT]", pcmStream,
&pcmStream);
/*auto sampleFormatValue =*/ op.add<Value<string>>("", "sampleformat", "Default sample format", settings.sampleFormat, &settings.sampleFormat); /*auto sampleFormatValue =*/op.add<Value<string>>("", "sampleformat", "Default sample format", settings.sampleFormat, &settings.sampleFormat);
/*auto codecValue =*/ op.add<Value<string>>("c", "codec", "Default transport codec\n(flac|ogg|pcm)[:options]\nType codec:? to get codec specific options", settings.codec, &settings.codec); /*auto codecValue =*/op.add<Value<string>>(
/*auto streamBufferValue =*/ op.add<Value<size_t>>("", "streamBuffer", "Default stream read buffer [ms]", settings.streamReadMs, &settings.streamReadMs); "c", "codec", "Default transport codec\n(flac|ogg|pcm)[:options]\nType codec:? to get codec specific options", settings.codec, &settings.codec);
/*auto bufferValue =*/ op.add<Value<int>>("b", "buffer", "Buffer [ms]", settings.bufferMs, &settings.bufferMs); /*auto streamBufferValue =*/op.add<Value<size_t>>("", "streamBuffer", "Default stream read buffer [ms]", settings.streamReadMs, &settings.streamReadMs);
/*auto muteSwitch =*/ op.add<Switch>("", "sendToMuted", "Send audio to muted clients", &settings.sendAudioToMutedClients); /*auto bufferValue =*/op.add<Value<int>>("b", "buffer", "Buffer [ms]", settings.bufferMs, &settings.bufferMs);
/*auto muteSwitch =*/op.add<Switch>("", "sendToMuted", "Send audio to muted clients", &settings.sendAudioToMutedClients);
#ifdef HAS_DAEMON #ifdef HAS_DAEMON
int processPriority(0); int processPriority(0);
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "Daemonize\noptional process priority [-20..19]", 0, &processPriority); auto daemonOption = op.add<Implicit<int>>("d", "daemon", "Daemonize\noptional process priority [-20..19]", 0, &processPriority);
@ -116,7 +119,7 @@ int main(int argc, char* argv[])
if (!streamValue->is_set()) if (!streamValue->is_set())
settings.pcmStreams.push_back(streamValue->value()); settings.pcmStreams.push_back(streamValue->value());
for (size_t n=0; n<streamValue->count(); ++n) for (size_t n = 0; n < streamValue->count(); ++n)
{ {
cout << streamValue->value(n) << "\n"; cout << streamValue->value(n) << "\n";
settings.pcmStreams.push_back(streamValue->value(n)); settings.pcmStreams.push_back(streamValue->value(n));
@ -140,7 +143,8 @@ int main(int argc, char* argv[])
{ {
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
if (!debugOption->value().empty()) if (!debugOption->value().empty())
AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(), "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(),
"%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
} }
else else
{ {
@ -190,11 +194,8 @@ int main(int argc, char* argv[])
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR) #if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
PublishZeroConf publishZeroConfg("Snapcast"); PublishZeroConf publishZeroConfg("Snapcast");
publishZeroConfg.publish({ publishZeroConfg.publish({mDNSService("_snapcast._tcp", settings.port), mDNSService("_snapcast-jsonrpc._tcp", settings.controlPort),
mDNSService("_snapcast._tcp", settings.port), mDNSService("_snapcastjsonrpc._tcp", settings.controlPort)});
mDNSService("_snapcast-jsonrpc._tcp", settings.controlPort),
mDNSService("_snapcastjsonrpc._tcp", settings.controlPort)
});
#endif #endif
if (settings.bufferMs < 400) if (settings.bufferMs < 400)
@ -204,7 +205,7 @@ int main(int argc, char* argv[])
std::unique_ptr<StreamServer> streamServer(new StreamServer(&io_service, settings)); std::unique_ptr<StreamServer> streamServer(new StreamServer(&io_service, settings));
streamServer->start(); streamServer->start();
auto func = [](asio::io_service* ioservice)->void{ioservice->run();}; auto func = [](asio::io_service* ioservice) -> void { ioservice->run(); };
std::thread t(func, &io_service); std::thread t(func, &io_service);
while (!g_terminated) while (!g_terminated)
@ -226,4 +227,3 @@ int main(int argc, char* argv[])
SLOG(NOTICE) << "daemon terminated." << endl; SLOG(NOTICE) << "daemon terminated." << endl;
exit(exitcode); exit(exitcode);
} }

View file

@ -17,11 +17,11 @@
***/ ***/
#include "streamServer.h" #include "streamServer.h"
#include "message/time.h"
#include "message/hello.h"
#include "message/streamTags.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "config.h" #include "config.h"
#include "message/hello.h"
#include "message/streamTags.h"
#include "message/time.h"
#include <iostream> #include <iostream>
using namespace std; using namespace std;
@ -29,11 +29,8 @@ using namespace std;
using json = nlohmann::json; using json = nlohmann::json;
StreamServer::StreamServer(asio::io_service* io_service, const StreamServerSettings& streamServerSettings) : StreamServer::StreamServer(asio::io_service* io_service, const StreamServerSettings& streamServerSettings)
io_service_(io_service), : io_service_(io_service), acceptor_v4_(nullptr), acceptor_v6_(nullptr), settings_(streamServerSettings)
acceptor_v4_(nullptr),
acceptor_v6_(nullptr),
settings_(streamServerSettings)
{ {
} }
@ -45,11 +42,12 @@ StreamServer::~StreamServer()
void StreamServer::onMetaChanged(const PcmStream* pcmStream) void StreamServer::onMetaChanged(const PcmStream* pcmStream)
{ {
/// Notification: {"jsonrpc":"2.0","method":"Stream.OnMetadata","params":{"id":"stream 1", "meta": {"album": "some album", "artist": "some artist", "track": "some track"...}} /// Notification: {"jsonrpc":"2.0","method":"Stream.OnMetadata","params":{"id":"stream 1", "meta": {"album": "some album", "artist": "some artist", "track":
/// "some track"...}}
// Send meta to all connected clients // Send meta to all connected clients
const auto meta = pcmStream->getMeta(); const auto meta = pcmStream->getMeta();
//cout << "metadata = " << meta->msg.dump(3) << "\n"; // cout << "metadata = " << meta->msg.dump(3) << "\n";
for (auto s : sessions_) for (auto s : sessions_)
{ {
@ -65,9 +63,11 @@ void StreamServer::onMetaChanged(const PcmStream* pcmStream)
void StreamServer::onStateChanged(const PcmStream* pcmStream, const ReaderState& state) void StreamServer::onStateChanged(const PcmStream* pcmStream, const ReaderState& state)
{ {
/// Notification: {"jsonrpc":"2.0","method":"Stream.OnUpdate","params":{"id":"stream 1","stream":{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}}}} /// Notification: {"jsonrpc":"2.0","method":"Stream.OnUpdate","params":{"id":"stream 1","stream":{"id":"stream
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}}}}
LOG(INFO) << "onStateChanged (" << pcmStream->getName() << "): " << state << "\n"; LOG(INFO) << "onStateChanged (" << pcmStream->getName() << "): " << state << "\n";
// LOG(INFO) << pcmStream->toJson().dump(4); // LOG(INFO) << pcmStream->toJson().dump(4);
json notification = jsonrpcpp::Notification("Stream.OnUpdate", jsonrpcpp::Parameter("id", pcmStream->getId(), "stream", pcmStream->toJson())).to_json(); json notification = jsonrpcpp::Notification("Stream.OnUpdate", jsonrpcpp::Parameter("id", pcmStream->getId(), "stream", pcmStream->toJson())).to_json();
controlServer_->send(notification.dump(), NULL); controlServer_->send(notification.dump(), NULL);
////cout << "Notification: " << notification.dump() << "\n"; ////cout << "Notification: " << notification.dump() << "\n";
@ -76,7 +76,7 @@ void StreamServer::onStateChanged(const PcmStream* pcmStream, const ReaderState&
void StreamServer::onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration) void StreamServer::onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration)
{ {
// LOG(INFO) << "onChunkRead (" << pcmStream->getName() << "): " << duration << "ms\n"; // LOG(INFO) << "onChunkRead (" << pcmStream->getName() << "): " << duration << "ms\n";
bool isDefaultStream(pcmStream == streamManager_->getDefaultStream().get()); bool isDefaultStream(pcmStream == streamManager_->getDefaultStream().get());
msg::message_ptr shared_message(chunk); msg::message_ptr shared_message(chunk);
@ -97,7 +97,7 @@ void StreamServer::onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk,
} }
} }
if (!s->pcmStream() && isDefaultStream)//->getName() == "default") if (!s->pcmStream() && isDefaultStream) //->getName() == "default")
s->sendAsync(shared_message); s->sendAsync(shared_message);
else if (s->pcmStream().get() == pcmStream) else if (s->pcmStream().get() == pcmStream)
s->sendAsync(shared_message); s->sendAsync(shared_message);
@ -122,7 +122,7 @@ void StreamServer::onDisconnect(StreamSession* streamSession)
LOG(INFO) << "onDisconnect: " << session->clientId << "\n"; LOG(INFO) << "onDisconnect: " << session->clientId << "\n";
LOG(DEBUG) << "sessions: " << sessions_.size() << "\n"; LOG(DEBUG) << "sessions: " << sessions_.size() << "\n";
// don't block: remove StreamSession in a thread // don't block: remove StreamSession in a thread
auto func = [](shared_ptr<StreamSession> s)->void{s->stop();}; auto func = [](shared_ptr<StreamSession> s) -> void { s->stop(); };
std::thread t(func, session); std::thread t(func, session);
t.detach(); t.detach();
sessions_.erase(session); sessions_.erase(session);
@ -144,8 +144,12 @@ void StreamServer::onDisconnect(StreamSession* streamSession)
/// in case of a duplicate client id /// in case of a duplicate client id
if (getStreamSession(clientInfo->id) == nullptr) if (getStreamSession(clientInfo->id) == nullptr)
{ {
/// Notification: {"jsonrpc":"2.0","method":"Client.OnDisconnect","params":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":false,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025523,"usec":814067},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},"id":"00:21:6a:7d:74:fc"}} /// Notification:
json notification = jsonrpcpp::Notification("Client.OnDisconnect", jsonrpcpp::Parameter("id", clientInfo->id, "client", clientInfo->toJson())).to_json(); /// {"jsonrpc":"2.0","method":"Client.OnDisconnect","params":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":false,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025523,"usec":814067},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},"id":"00:21:6a:7d:74:fc"}}
json notification =
jsonrpcpp::Notification("Client.OnDisconnect", jsonrpcpp::Parameter("id", clientInfo->id, "client", clientInfo->toJson())).to_json();
controlServer_->send(notification.dump()); controlServer_->send(notification.dump());
////cout << "Notification: " << notification.dump() << "\n"; ////cout << "Notification: " << notification.dump() << "\n";
} }
@ -169,7 +173,10 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
if (request->method() == "Client.GetStatus") if (request->method() == "Client.GetStatus")
{ {
/// Request: {"id":8,"jsonrpc":"2.0","method":"Client.GetStatus","params":{"id":"00:21:6a:7d:74:fc"}} /// Request: {"id":8,"jsonrpc":"2.0","method":"Client.GetStatus","params":{"id":"00:21:6a:7d:74:fc"}}
/// Response: {"id":8,"jsonrpc":"2.0","result":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026416,"usec":135973},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}}} /// Response:
/// {"id":8,"jsonrpc":"2.0","result":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026416,"usec":135973},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}}}
result["client"] = clientInfo->toJson(); result["client"] = clientInfo->toJson();
} }
else if (request->method() == "Client.SetVolume") else if (request->method() == "Client.SetVolume")
@ -179,7 +186,8 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
/// Notification: {"jsonrpc":"2.0","method":"Client.OnVolumeChanged","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}} /// Notification: {"jsonrpc":"2.0","method":"Client.OnVolumeChanged","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}}
clientInfo->config.volume.fromJson(request->params().get("volume")); clientInfo->config.volume.fromJson(request->params().get("volume"));
result["volume"] = clientInfo->config.volume.toJson(); result["volume"] = clientInfo->config.volume.toJson();
notification.reset(new jsonrpcpp::Notification("Client.OnVolumeChanged", jsonrpcpp::Parameter("id", clientInfo->id, "volume", clientInfo->config.volume.toJson()))); notification.reset(new jsonrpcpp::Notification("Client.OnVolumeChanged",
jsonrpcpp::Parameter("id", clientInfo->id, "volume", clientInfo->config.volume.toJson())));
} }
else if (request->method() == "Client.SetLatency") else if (request->method() == "Client.SetLatency")
{ {
@ -193,7 +201,8 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
latency = settings_.bufferMs; latency = settings_.bufferMs;
clientInfo->config.latency = latency; //, -10000, settings_.bufferMs); clientInfo->config.latency = latency; //, -10000, settings_.bufferMs);
result["latency"] = clientInfo->config.latency; result["latency"] = clientInfo->config.latency;
notification.reset(new jsonrpcpp::Notification("Client.OnLatencyChanged", jsonrpcpp::Parameter("id", clientInfo->id, "latency", clientInfo->config.latency))); notification.reset(
new jsonrpcpp::Notification("Client.OnLatencyChanged", jsonrpcpp::Parameter("id", clientInfo->id, "latency", clientInfo->config.latency)));
} }
else if (request->method() == "Client.SetName") else if (request->method() == "Client.SetName")
{ {
@ -202,7 +211,8 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
/// Notification: {"jsonrpc":"2.0","method":"Client.OnNameChanged","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}} /// Notification: {"jsonrpc":"2.0","method":"Client.OnNameChanged","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}}
clientInfo->config.name = request->params().get<std::string>("name"); clientInfo->config.name = request->params().get<std::string>("name");
result["name"] = clientInfo->config.name; result["name"] = clientInfo->config.name;
notification.reset(new jsonrpcpp::Notification("Client.OnNameChanged", jsonrpcpp::Parameter("id", clientInfo->id, "name", clientInfo->config.name))); notification.reset(
new jsonrpcpp::Notification("Client.OnNameChanged", jsonrpcpp::Parameter("id", clientInfo->id, "name", clientInfo->config.name)));
} }
else else
throw jsonrpcpp::MethodNotFoundException(request->id()); throw jsonrpcpp::MethodNotFoundException(request->id());
@ -233,7 +243,13 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
if (request->method() == "Group.GetStatus") if (request->method() == "Group.GetStatus")
{ {
/// Request: {"id":5,"jsonrpc":"2.0","method":"Group.GetStatus","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}} /// Request: {"id":5,"jsonrpc":"2.0","method":"Group.GetStatus","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
/// Response: {"id":5,"jsonrpc":"2.0","result":{"group":{"clients":[{"config":{"instance":2,"latency":10,"name":"Laptop","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488026485,"usec":644997},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026481,"usec":223747},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":true,"name":"","stream_id":"stream 1"}}} /// Response:
/// {"id":5,"jsonrpc":"2.0","result":{"group":{"clients":[{"config":{"instance":2,"latency":10,"name":"Laptop","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488026485,"usec":644997},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026481,"usec":223747},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":true,"name":"","stream_id":"stream
/// 1"}}}
result["group"] = group->toJson(); result["group"] = group->toJson();
} }
else if (request->method() == "Group.SetMute") else if (request->method() == "Group.SetMute")
@ -245,7 +261,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
group->muted = muted; group->muted = muted;
/// Update clients /// Update clients
for (auto client: group->clients) for (auto client : group->clients)
{ {
session_ptr session = getStreamSession(client->id); session_ptr session = getStreamSession(client->id);
if (session != nullptr) if (session != nullptr)
@ -265,9 +281,11 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
} }
else if (request->method() == "Group.SetStream") else if (request->method() == "Group.SetStream")
{ {
/// Request: {"id":4,"jsonrpc":"2.0","method":"Group.SetStream","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream 1"}} /// Request: {"id":4,"jsonrpc":"2.0","method":"Group.SetStream","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream
/// 1"}}
/// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"stream 1"}} /// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"stream 1"}}
/// Notification: {"jsonrpc":"2.0","method":"Group.OnStreamChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream 1"}} /// Notification: {"jsonrpc":"2.0","method":"Group.OnStreamChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream
/// 1"}}
string streamId = request->params().get<std::string>("stream_id"); string streamId = request->params().get<std::string>("stream_id");
PcmStreamPtr stream = streamManager_->getStream(streamId); PcmStreamPtr stream = streamManager_->getStream(streamId);
if (stream == nullptr) if (stream == nullptr)
@ -276,7 +294,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
group->streamId = streamId; group->streamId = streamId;
/// Update clients /// Update clients
for (auto client: group->clients) for (auto client : group->clients)
{ {
session_ptr session = getStreamSession(client->id); session_ptr session = getStreamSession(client->id);
if (session && (session->pcmStream() != stream)) if (session && (session->pcmStream() != stream))
@ -293,9 +311,33 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
} }
else if (request->method() == "Group.SetClients") else if (request->method() == "Group.SetClients")
{ {
/// Request: {"id":3,"jsonrpc":"2.0","method":"Group.SetClients","params":{"clients":["00:21:6a:7d:74:fc#2","00:21:6a:7d:74:fc"],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}} /// Request:
/// Response: {"id":3,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} /// {"id":3,"jsonrpc":"2.0","method":"Group.SetClients","params":{"clients":["00:21:6a:7d:74:fc#2","00:21:6a:7d:74:fc"],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
/// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} /// Response: {"id":3,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
/// Notification:
/// {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
vector<string> clients = request->params().get("clients"); vector<string> clients = request->params().get("clients");
/// Remove clients from group /// Remove clients from group
for (auto iter = group->clients.begin(); iter != group->clients.end();) for (auto iter = group->clients.begin(); iter != group->clients.end();)
@ -313,7 +355,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
/// Add clients to group /// Add clients to group
PcmStreamPtr stream = streamManager_->getStream(group->streamId); PcmStreamPtr stream = streamManager_->getStream(group->streamId);
for (const auto& clientId: clients) for (const auto& clientId : clients)
{ {
ClientInfoPtr client = Config::instance().getClientInfo(clientId); ClientInfoPtr client = Config::instance().getClientInfo(clientId);
if (!client) if (!client)
@ -368,14 +410,44 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
else if (request->method() == "Server.GetStatus") else if (request->method() == "Server.GetStatus")
{ {
/// Request: {"id":1,"jsonrpc":"2.0","method":"Server.GetStatus"} /// Request: {"id":1,"jsonrpc":"2.0","method":"Server.GetStatus"}
/// Response: {"id":1,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025696,"usec":578142},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025696,"usec":611255},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} /// Response: {"id":1,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025696,"usec":578142},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025696,"usec":611255},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
result["server"] = Config::instance().getServerStatus(streamManager_->toJson()); result["server"] = Config::instance().getServerStatus(streamManager_->toJson());
} }
else if (request->method() == "Server.DeleteClient") else if (request->method() == "Server.DeleteClient")
{ {
/// Request: {"id":2,"jsonrpc":"2.0","method":"Server.DeleteClient","params":{"id":"00:21:6a:7d:74:fc"}} /// Request: {"id":2,"jsonrpc":"2.0","method":"Server.DeleteClient","params":{"id":"00:21:6a:7d:74:fc"}}
/// Response: {"id":2,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} /// Response: {"id":2,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
/// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} /// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
/// Notification:
/// {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get<std::string>("id")); ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get<std::string>("id"));
if (clientInfo == nullptr) if (clientInfo == nullptr)
throw jsonrpcpp::InternalErrorException("Client not found", request->id()); throw jsonrpcpp::InternalErrorException("Client not found", request->id());
@ -401,7 +473,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
/// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"Spotify"}} /// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"Spotify"}}
/// Call onMetaChanged(const PcmStream* pcmStream) for updates and notifications /// Call onMetaChanged(const PcmStream* pcmStream) for updates and notifications
LOG(INFO) << "Stream.SetMeta(" << request->params().get<std::string>("id") << ")" << request->params().get("meta") <<"\n"; LOG(INFO) << "Stream.SetMeta(" << request->params().get<std::string>("id") << ")" << request->params().get("meta") << "\n";
// Find stream // Find stream
string streamId = request->params().get<std::string>("id"); string streamId = request->params().get<std::string>("id");
@ -447,12 +519,12 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std::
if (!entity) if (!entity)
return; return;
} }
catch(const jsonrpcpp::ParseErrorException& e) catch (const jsonrpcpp::ParseErrorException& e)
{ {
controlSession->send(e.to_json().dump()); controlSession->send(e.to_json().dump());
return; return;
} }
catch(const std::exception& e) catch (const std::exception& e)
{ {
controlSession->send(jsonrpcpp::ParseErrorException(e.what()).to_json().dump()); controlSession->send(jsonrpcpp::ParseErrorException(e.what()).to_json().dump());
return; return;
@ -482,7 +554,7 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std::
////cout << "Batch: " << batch->to_json().dump() << "\n"; ////cout << "Batch: " << batch->to_json().dump() << "\n";
jsonrpcpp::Batch responseBatch; jsonrpcpp::Batch responseBatch;
jsonrpcpp::Batch notificationBatch; jsonrpcpp::Batch notificationBatch;
for (const auto& batch_entity: batch->entities) for (const auto& batch_entity : batch->entities)
{ {
if (batch_entity->is_request()) if (batch_entity->is_request())
{ {
@ -505,14 +577,17 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std::
void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::BaseMessage& baseMessage, char* buffer) void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::BaseMessage& baseMessage, char* buffer)
{ {
// LOG(DEBUG) << "onMessageReceived: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << ", sent: " << baseMessage.sent.sec << "," << baseMessage.sent.usec << ", recv: " << baseMessage.received.sec << "," << baseMessage.received.usec << "\n"; // LOG(DEBUG) << "onMessageReceived: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " <<
//baseMessage.refersTo << ", sent: " << baseMessage.sent.sec << "," << baseMessage.sent.usec << ", recv: " << baseMessage.received.sec << "," <<
//baseMessage.received.usec << "\n";
if (baseMessage.type == message_type::kTime) if (baseMessage.type == message_type::kTime)
{ {
auto timeMsg = make_shared<msg::Time>(); auto timeMsg = make_shared<msg::Time>();
timeMsg->deserialize(baseMessage, buffer); timeMsg->deserialize(baseMessage, buffer);
timeMsg->refersTo = timeMsg->id; timeMsg->refersTo = timeMsg->id;
timeMsg->latency = timeMsg->received - timeMsg->sent; timeMsg->latency = timeMsg->received - timeMsg->sent;
// LOG(INFO) << "Latency sec: " << timeMsg.latency.sec << ", usec: " << timeMsg.latency.usec << ", refers to: " << timeMsg.refersTo << "\n"; // LOG(INFO) << "Latency sec: " << timeMsg.latency.sec << ", usec: " << timeMsg.latency.usec << ", refers to: " << timeMsg.refersTo <<
//"\n";
streamSession->sendAsync(timeMsg); streamSession->sendAsync(timeMsg);
// refresh streamSession state // refresh streamSession state
@ -533,7 +608,7 @@ void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::Ba
<< ", Protocol version: " << helloMsg.getProtocolVersion() << "\n"; << ", Protocol version: " << helloMsg.getProtocolVersion() << "\n";
LOG(DEBUG) << "request kServerSettings: " << streamSession->clientId << "\n"; LOG(DEBUG) << "request kServerSettings: " << streamSession->clientId << "\n";
// std::lock_guard<std::mutex> mlock(mutex_); // std::lock_guard<std::mutex> mlock(mutex_);
bool newGroup(false); bool newGroup(false);
GroupPtr group = Config::instance().getGroupFromClient(streamSession->clientId); GroupPtr group = Config::instance().getGroupFromClient(streamSession->clientId);
if (group == nullptr) if (group == nullptr)
@ -583,7 +658,20 @@ void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::Ba
if (newGroup) if (newGroup)
{ {
/// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025796,"usec":714671},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"},{"clients":[{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025798,"usec":728305},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"c5da8f7a-f377-1e51-8266-c5cc61099b71","muted":false,"name":"","stream_id":"stream 1"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}} /// Notification:
/// {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025796,"usec":714671},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
/// 2"},{"clients":[{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025798,"usec":728305},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"c5da8f7a-f377-1e51-8266-c5cc61099b71","muted":false,"name":"","stream_id":"stream
/// 1"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
json server = Config::instance().getServerStatus(streamManager_->toJson()); json server = Config::instance().getServerStatus(streamManager_->toJson());
json notification = jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server)).to_json(); json notification = jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server)).to_json();
controlServer_->send(notification.dump()); controlServer_->send(notification.dump());
@ -591,13 +679,16 @@ void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::Ba
} }
else else
{ {
/// Notification: {"jsonrpc":"2.0","method":"Client.OnConnect","params":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025524,"usec":876332},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},"id":"00:21:6a:7d:74:fc"}} /// Notification:
/// {"jsonrpc":"2.0","method":"Client.OnConnect","params":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
/// Mint 17.3
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025524,"usec":876332},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},"id":"00:21:6a:7d:74:fc"}}
json notification = jsonrpcpp::Notification("Client.OnConnect", jsonrpcpp::Parameter("id", client->id, "client", client->toJson())).to_json(); json notification = jsonrpcpp::Notification("Client.OnConnect", jsonrpcpp::Parameter("id", client->id, "client", client->toJson())).to_json();
controlServer_->send(notification.dump()); controlServer_->send(notification.dump());
////cout << "Notification: " << notification.dump() << "\n"; ////cout << "Notification: " << notification.dump() << "\n";
} }
// cout << Config::instance().getServerStatus(streamManager_->toJson()).dump(4) << "\n"; // cout << Config::instance().getServerStatus(streamManager_->toJson()).dump(4) << "\n";
// cout << group->toJson().dump(4) << "\n"; // cout << group->toJson().dump(4) << "\n";
} }
} }
@ -606,7 +697,7 @@ void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::Ba
session_ptr StreamServer::getStreamSession(StreamSession* streamSession) const session_ptr StreamServer::getStreamSession(StreamSession* streamSession) const
{ {
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_); std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
for (auto session: sessions_) for (auto session : sessions_)
{ {
if (session.get() == streamSession) if (session.get() == streamSession)
return session; return session;
@ -617,9 +708,9 @@ session_ptr StreamServer::getStreamSession(StreamSession* streamSession) const
session_ptr StreamServer::getStreamSession(const std::string& clientId) const session_ptr StreamServer::getStreamSession(const std::string& clientId) const
{ {
// LOG(INFO) << "getStreamSession: " << mac << "\n"; // LOG(INFO) << "getStreamSession: " << mac << "\n";
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_); std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
for (auto session: sessions_) for (auto session : sessions_)
{ {
if (session->clientId == clientId) if (session->clientId == clientId)
return session; return session;
@ -681,8 +772,8 @@ void StreamServer::start()
controlServer_->start(); controlServer_->start();
streamManager_.reset(new StreamManager(this, settings_.sampleFormat, settings_.codec, settings_.streamReadMs)); streamManager_.reset(new StreamManager(this, settings_.sampleFormat, settings_.codec, settings_.streamReadMs));
// throw SnapException("xxx"); // throw SnapException("xxx");
for (const auto& streamUri: settings_.pcmStreams) for (const auto& streamUri : settings_.pcmStreams)
{ {
PcmStreamPtr stream = streamManager_->addStream(streamUri); PcmStreamPtr stream = streamManager_->addStream(streamUri);
if (stream) if (stream)
@ -741,7 +832,7 @@ void StreamServer::stop()
{ {
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_); std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
for (auto session: sessions_)//it = sessions_.begin(); it != sessions_.end(); ++it) for (auto session : sessions_) // it = sessions_.begin(); it != sessions_.end(); ++it)
{ {
if (session) if (session)
{ {
@ -769,4 +860,3 @@ void StreamServer::stop()
acceptor_v6_ = nullptr; acceptor_v6_ = nullptr;
} }
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -20,22 +20,22 @@
#define STREAM_SERVER_H #define STREAM_SERVER_H
#include <asio.hpp> #include <asio.hpp>
#include <vector>
#include <thread>
#include <memory> #include <memory>
#include <mutex>
#include <set> #include <set>
#include <sstream> #include <sstream>
#include <mutex> #include <thread>
#include <vector>
#include "jsonrpcpp.hpp"
#include "streamSession.h"
#include "streamreader/streamManager.h"
#include "common/queue.h" #include "common/queue.h"
#include "common/sampleFormat.h" #include "common/sampleFormat.h"
#include "message/message.h"
#include "message/codecHeader.h"
#include "message/serverSettings.h"
#include "controlServer.h" #include "controlServer.h"
#include "jsonrpcpp.hpp"
#include "message/codecHeader.h"
#include "message/message.h"
#include "message/serverSettings.h"
#include "streamSession.h"
#include "streamreader/streamManager.h"
using asio::ip::tcp; using asio::ip::tcp;
@ -44,14 +44,8 @@ typedef std::shared_ptr<StreamSession> session_ptr;
struct StreamServerSettings struct StreamServerSettings
{ {
StreamServerSettings() : StreamServerSettings()
port(1704), : port(1704), controlPort(1705), codec("flac"), bufferMs(1000), sampleFormat("48000:16:2"), streamReadMs(20), sendAudioToMutedClients(false)
controlPort(1705),
codec("flac"),
bufferMs(1000),
sampleFormat("48000:16:2"),
streamReadMs(20),
sendAudioToMutedClients(false)
{ {
} }
size_t port; size_t port;
@ -82,7 +76,7 @@ public:
void stop(); void stop();
/// Send a message to all connceted clients /// Send a message to all connceted clients
// void send(const msg::BaseMessage* message); // void send(const msg::BaseMessage* message);
/// Clients call this when they receive a message. Implementation of MessageReceiver::onMessageReceived /// Clients call this when they receive a message. Implementation of MessageReceiver::onMessageReceived
virtual void onMessageReceived(StreamSession* connection, const msg::BaseMessage& baseMessage, char* buffer); virtual void onMessageReceived(StreamSession* connection, const msg::BaseMessage& baseMessage, char* buffer);
@ -118,5 +112,3 @@ private:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -18,17 +18,17 @@
#include "streamSession.h" #include "streamSession.h"
#include <iostream>
#include <mutex>
#include "aixlog.hpp" #include "aixlog.hpp"
#include "message/pcmChunk.h" #include "message/pcmChunk.h"
#include <iostream>
#include <mutex>
using namespace std; using namespace std;
StreamSession::StreamSession(MessageReceiver* receiver, std::shared_ptr<tcp::socket> socket) : StreamSession::StreamSession(MessageReceiver* receiver, std::shared_ptr<tcp::socket> socket)
active_(false), readerThread_(nullptr), writerThread_(nullptr), messageReceiver_(receiver), pcmStream_(nullptr) : active_(false), readerThread_(nullptr), writerThread_(nullptr), messageReceiver_(receiver), pcmStream_(nullptr)
{ {
socket_ = socket; socket_ = socket;
} }
@ -80,9 +80,11 @@ void StreamSession::stop()
{ {
std::lock_guard<std::mutex> socketLock(socketMutex_); std::lock_guard<std::mutex> socketLock(socketMutex_);
socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec);
if (ec) LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n"; if (ec)
LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n";
socket_->close(ec); socket_->close(ec);
if (ec) LOG(ERROR) << "Error in socket close: " << ec.message() << "\n"; if (ec)
LOG(ERROR) << "Error in socket close: " << ec.message() << "\n";
} }
if (readerThread_ && readerThread_->joinable()) if (readerThread_ && readerThread_->joinable())
{ {
@ -96,7 +98,7 @@ void StreamSession::stop()
writerThread_->join(); writerThread_->join();
} }
} }
catch(...) catch (...)
{ {
} }
@ -113,8 +115,7 @@ void StreamSession::socketRead(void* _to, size_t _bytes)
do do
{ {
read += socket_->read_some(asio::buffer((char*)_to + read, _bytes - read)); read += socket_->read_some(asio::buffer((char*)_to + read, _bytes - read));
} } while (active_ && (read < _bytes));
while (active_ && (read < _bytes));
} }
@ -123,8 +124,8 @@ void StreamSession::sendAsync(const msg::message_ptr& message, bool sendNow)
if (!message) if (!message)
return; return;
//the writer will take care about old messages // the writer will take care about old messages
while (messages_.size() > 2000)// chunk->getDuration() > 10000) while (messages_.size() > 2000) // chunk->getDuration() > 10000)
messages_.pop(); messages_.pop();
if (sendNow) if (sendNow)
@ -148,8 +149,8 @@ void StreamSession::setBufferMs(size_t bufferMs)
bool StreamSession::send(const msg::message_ptr& message) const bool StreamSession::send(const msg::message_ptr& message) const
{ {
//TODO on exception: set active = false // TODO on exception: set active = false
// LOG(INFO) << "send: " << message->type << ", size: " << message->getSize() << ", id: " << message->id << ", refers: " << message->refersTo << "\n"; // LOG(INFO) << "send: " << message->type << ", size: " << message->getSize() << ", id: " << message->id << ", refers: " << message->refersTo << "\n";
std::lock_guard<std::mutex> socketLock(socketMutex_); std::lock_guard<std::mutex> socketLock(socketMutex_);
{ {
std::lock_guard<std::mutex> activeLock(activeMutex_); std::lock_guard<std::mutex> activeLock(activeMutex_);
@ -162,7 +163,7 @@ bool StreamSession::send(const msg::message_ptr& message) const
message->sent = t; message->sent = t;
message->serialize(stream); message->serialize(stream);
asio::write(*socket_.get(), streambuf); asio::write(*socket_.get(), streambuf);
// LOG(INFO) << "done: " << message->type << ", size: " << message->size << ", id: " << message->id << ", refers: " << message->refersTo << "\n"; // LOG(INFO) << "done: " << message->type << ", size: " << message->size << ", id: " << message->id << ", refers: " << message->refersTo << "\n";
return true; return true;
} }
@ -188,13 +189,14 @@ void StreamSession::getNextMessage()
throw std::runtime_error(ss.str().c_str()); throw std::runtime_error(ss.str().c_str());
} }
// LOG(INFO) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << "\n"; // LOG(INFO) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " <<
//baseMessage.refersTo << "\n";
if (baseMessage.size > buffer.size()) if (baseMessage.size > buffer.size())
buffer.resize(baseMessage.size); buffer.resize(baseMessage.size);
// { // {
// std::lock_guard<std::mutex> socketLock(socketMutex_); // std::lock_guard<std::mutex> socketLock(socketMutex_);
socketRead(&buffer[0], baseMessage.size); socketRead(&buffer[0], baseMessage.size);
// } // }
tv t; tv t;
baseMessage.received = t; baseMessage.received = t;
@ -242,7 +244,7 @@ void StreamSession::writer()
size_t age = 0; size_t age = 0;
if (now > wireChunk->start()) if (now > wireChunk->start())
age = std::chrono::duration_cast<chronos::msec>(now - wireChunk->start()).count(); age = std::chrono::duration_cast<chronos::msec>(now - wireChunk->start()).count();
//LOG(DEBUG) << "PCM chunk. Age: " << age << ", buffer: " << bufferMs_ << ", age > buffer: " << (age > bufferMs_) << "\n"; // LOG(DEBUG) << "PCM chunk. Age: " << age << ", buffer: " << bufferMs_ << ", age > buffer: " << (age > bufferMs_) << "\n";
if (age > bufferMs_) if (age > bufferMs_)
continue; continue;
} }
@ -259,5 +261,3 @@ void StreamSession::writer()
if (active_ && (messageReceiver_ != NULL)) if (active_ && (messageReceiver_ != NULL))
messageReceiver_->onDisconnect(this); messageReceiver_->onDisconnect(this);
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,17 +19,17 @@
#ifndef STREAM_SESSION_H #ifndef STREAM_SESSION_H
#define STREAM_SESSION_H #define STREAM_SESSION_H
#include "common/queue.h"
#include "message/message.h"
#include "streamreader/streamManager.h"
#include <asio.hpp>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <set>
#include <string> #include <string>
#include <thread> #include <thread>
#include <atomic>
#include <memory>
#include <asio.hpp>
#include <condition_variable>
#include <set>
#include <mutex>
#include "message/message.h"
#include "common/queue.h"
#include "streamreader/streamManager.h"
using asio::ip::tcp; using asio::ip::tcp;
@ -104,9 +104,4 @@ protected:
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -17,11 +17,11 @@
***/ ***/
#include "airplayStream.h" #include "airplayStream.h"
#include "common/snapException.h"
#include "common/utils/string_utils.h"
#include "common/utils.h"
#include "base64.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "base64.h"
#include "common/snapException.h"
#include "common/utils.h"
#include "common/utils/string_utils.h"
using namespace std; using namespace std;
@ -30,7 +30,7 @@ static string hex2str(string input)
typedef unsigned char byte; typedef unsigned char byte;
unsigned long x = strtoul(input.c_str(), 0, 16); unsigned long x = strtoul(input.c_str(), 0, 16);
byte a[] = {byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x), 0}; byte a[] = {byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x), 0};
return string((char *)a); return string((char*)a);
} }
/* /*
@ -46,7 +46,7 @@ AirplayStream::AirplayStream(PcmListener* pcmListener, const StreamUri& uri) : P
logStderr_ = true; logStderr_ = true;
pipePath_ = "/tmp/shairmeta." + cpt::to_string(getpid()); pipePath_ = "/tmp/shairmeta." + cpt::to_string(getpid());
//cout << "Pipe [" << pipePath_ << "]\n"; // cout << "Pipe [" << pipePath_ << "]\n";
// XXX: Check if pipe exists, delete or throw error // XXX: Check if pipe exists, delete or throw error
@ -78,7 +78,7 @@ int AirplayStream::parse(string line)
{ {
enum XML_Status result; enum XML_Status result;
if((result = XML_Parse(parser_, line.c_str(), line.length(), false)) == XML_STATUS_ERROR) if ((result = XML_Parse(parser_, line.c_str(), line.length(), false)) == XML_STATUS_ERROR)
{ {
XML_ParserFree(parser_); XML_ParserFree(parser_);
createParser(); createParser();
@ -100,18 +100,22 @@ void AirplayStream::createParser()
void AirplayStream::push() void AirplayStream::push()
{ {
string data = entry_->data; string data = entry_->data;
if(entry_->isBase64 && entry_->length > 0) if (entry_->isBase64 && entry_->length > 0)
data = base64_decode(data); data = base64_decode(data);
if(entry_->type == "ssnc" && entry_->code == "mdst") if (entry_->type == "ssnc" && entry_->code == "mdst")
jtag_ = json(); jtag_ = json();
if(entry_->code == "asal") jtag_["ALBUM"] = data; if (entry_->code == "asal")
if(entry_->code == "asar") jtag_["ARTIST"] = data; jtag_["ALBUM"] = data;
if(entry_->code == "minm") jtag_["TITLE"] = data; if (entry_->code == "asar")
jtag_["ARTIST"] = data;
if (entry_->code == "minm")
jtag_["TITLE"] = data;
if(entry_->type == "ssnc" && entry_->code == "mden"){ if (entry_->type == "ssnc" && entry_->code == "mden")
//LOG(INFO) << "metadata=" << jtag_.dump(4) << "\n"; {
// LOG(INFO) << "metadata=" << jtag_.dump(4) << "\n";
setMeta(jtag_); setMeta(jtag_);
} }
} }
@ -123,14 +127,16 @@ void AirplayStream::pipeReader()
createParser(); createParser();
#endif #endif
while(true) while (true)
{ {
ifstream pipe(pipePath_); ifstream pipe(pipePath_);
if(pipe){ if (pipe)
{
string line; string line;
while(getline(pipe, line)){ while (getline(pipe, line))
{
#ifdef HAS_EXPAT #ifdef HAS_EXPAT
parse(line); parse(line);
#endif #endif
@ -181,51 +187,54 @@ void AirplayStream::onStderrMsg(const char* buffer, size_t n)
} }
#ifdef HAS_EXPAT #ifdef HAS_EXPAT
void XMLCALL AirplayStream::element_start(void *userdata, const char *element_name, const char **attr) void XMLCALL AirplayStream::element_start(void* userdata, const char* element_name, const char** attr)
{ {
AirplayStream *self = (AirplayStream *)userdata; AirplayStream* self = (AirplayStream*)userdata;
string name(element_name); string name(element_name);
self->buf_.assign(""); self->buf_.assign("");
if(name == "item") self->entry_.reset(new TageEntry); if (name == "item")
self->entry_.reset(new TageEntry);
for(int i = 0; attr[i]; i += 2){ for (int i = 0; attr[i]; i += 2)
{
string name(attr[i]); string name(attr[i]);
string value(attr[i+1]); string value(attr[i + 1]);
if(name == "encoding") if (name == "encoding")
self->entry_->isBase64 = (value == "base64"); // Quick & dirty.. self->entry_->isBase64 = (value == "base64"); // Quick & dirty..
} }
} }
void XMLCALL AirplayStream::element_end(void *userdata, const char *element_name) void XMLCALL AirplayStream::element_end(void* userdata, const char* element_name)
{ {
AirplayStream *self = (AirplayStream *)userdata; AirplayStream* self = (AirplayStream*)userdata;
string name(element_name); string name(element_name);
if(name == "code") if (name == "code")
self->entry_->code.assign(hex2str(self->buf_)); self->entry_->code.assign(hex2str(self->buf_));
else if(name == "type") else if (name == "type")
self->entry_->type.assign(hex2str(self->buf_)); self->entry_->type.assign(hex2str(self->buf_));
else if(name == "length") else if (name == "length")
self->entry_->length = strtoul(self->buf_.c_str(), 0, 10); self->entry_->length = strtoul(self->buf_.c_str(), 0, 10);
else if(name == "data") else if (name == "data")
self->entry_->data = self->buf_; self->entry_->data = self->buf_;
else if(name == "item") else if (name == "item")
self->push(); self->push();
else if(name == "metatags") ; else if (name == "metatags")
else cout << "Unknown tag <" << name << ">\n"; ;
else
cout << "Unknown tag <" << name << ">\n";
} }
void XMLCALL AirplayStream::data(void *userdata, const char *content, int length) void XMLCALL AirplayStream::data(void* userdata, const char* content, int length)
{ {
AirplayStream *self = (AirplayStream *)userdata; AirplayStream* self = (AirplayStream*)userdata;
string value(content, (size_t)length); string value(content, (size_t)length);
self->buf_.append(value); self->buf_.append(value);
} }
#endif #endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -32,7 +32,9 @@
class TageEntry class TageEntry
{ {
public: public:
TageEntry(): isBase64(false), length(0) {} TageEntry() : isBase64(false), length(0)
{
}
std::string code; std::string code;
std::string type; std::string type;
@ -80,9 +82,9 @@ protected:
std::thread pipeReaderThread_; std::thread pipeReaderThread_;
#ifdef HAS_EXPAT #ifdef HAS_EXPAT
static void XMLCALL element_start(void *userdata, const char *element_name, const char **attr); static void XMLCALL element_start(void* userdata, const char* element_name, const char** attr);
static void XMLCALL element_end(void *userdata, const char *element_name); static void XMLCALL element_end(void* userdata, const char* element_name);
static void XMLCALL data(void *userdata, const char *content, int length); static void XMLCALL data(void* userdata, const char* content, int length);
#endif #endif
}; };

View file

@ -26,32 +26,35 @@
*/ */
#include "base64.h" #include "base64.h"
static const std::string base64_chars = static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyz"
"0123456789+/"; "0123456789+/";
static inline bool is_base64(unsigned char c) { static inline bool is_base64(unsigned char c)
{
return (isalnum(c) || (c == '+') || (c == '/')); return (isalnum(c) || (c == '+') || (c == '/'));
} }
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len)
{
std::string ret; std::string ret;
int i = 0; int i = 0;
int j = 0; int j = 0;
unsigned char char_array_3[3]; unsigned char char_array_3[3];
unsigned char char_array_4[4]; unsigned char char_array_4[4];
while (in_len--) { while (in_len--)
{
char_array_3[i++] = *(bytes_to_encode++); char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) { if (i == 3)
{
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f; char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++) for (i = 0; (i < 4); i++)
ret += base64_chars[char_array_4[i]]; ret += base64_chars[char_array_4[i]];
i = 0; i = 0;
} }
@ -59,7 +62,7 @@ std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_
if (i) if (i)
{ {
for(j = i; j < 3; j++) for (j = i; j < 3; j++)
char_array_3[j] = '\0'; char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
@ -70,15 +73,14 @@ std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_
for (j = 0; (j < i + 1); j++) for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]]; ret += base64_chars[char_array_4[j]];
while((i++ < 3)) while ((i++ < 3))
ret += '='; ret += '=';
} }
return ret; return ret;
} }
std::string base64_decode(std::string const& encoded_string) { std::string base64_decode(std::string const& encoded_string)
{
int in_len = encoded_string.size(); int in_len = encoded_string.size();
int i = 0; int i = 0;
int j = 0; int j = 0;
@ -86,10 +88,13 @@ std::string base64_decode(std::string const& encoded_string) {
unsigned char char_array_4[4], char_array_3[3]; unsigned char char_array_4[4], char_array_3[3];
std::string ret; std::string ret;
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
char_array_4[i++] = encoded_string[in_]; in_++; {
if (i ==4) { char_array_4[i++] = encoded_string[in_];
for (i = 0; i <4; i++) in_++;
if (i == 4)
{
for (i = 0; i < 4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]); char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
@ -102,20 +107,21 @@ std::string base64_decode(std::string const& encoded_string) {
} }
} }
if (i) { if (i)
for (j = i; j <4; j++) {
for (j = i; j < 4; j++)
char_array_4[j] = 0; char_array_4[j] = 0;
for (j = 0; j <4; j++) for (j = 0; j < 4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]); char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; for (j = 0; (j < i - 1); j++)
ret += char_array_3[j];
} }
return ret; return ret;
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,24 +16,23 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <fcntl.h>
#include <memory> #include <memory>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h>
#include "fileStream.h"
#include "encoder/encoderFactory.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/snapException.h" #include "common/snapException.h"
#include "encoder/encoderFactory.h"
#include "fileStream.h"
using namespace std; using namespace std;
FileStream::FileStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri) FileStream::FileStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri)
{ {
ifs.open(uri_.path.c_str(), std::ifstream::in|std::ifstream::binary); ifs.open(uri_.path.c_str(), std::ifstream::in | std::ifstream::binary);
if (!ifs.good()) if (!ifs.good())
{ {
LOG(ERROR) << "failed to open PCM file: \"" + uri_.path + "\"\n"; LOG(ERROR) << "failed to open PCM file: \"" + uri_.path + "\"\n";
@ -53,9 +52,9 @@ void FileStream::worker()
timeval tvChunk; timeval tvChunk;
std::unique_ptr<msg::PcmChunk> chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_)); std::unique_ptr<msg::PcmChunk> chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_));
ifs.seekg (0, ifs.end); ifs.seekg(0, ifs.end);
size_t length = ifs.tellg(); size_t length = ifs.tellg();
ifs.seekg (0, ifs.beg); ifs.seekg(0, ifs.beg);
setState(kPlaying); setState(kPlaying);
@ -78,20 +77,21 @@ void FileStream::worker()
if (left < toRead) if (left < toRead)
{ {
ifs.read(chunk->payload, left); ifs.read(chunk->payload, left);
ifs.seekg (0, ifs.beg); ifs.seekg(0, ifs.beg);
count = left; count = left;
} }
ifs.read(chunk->payload + count, toRead - count); ifs.read(chunk->payload + count, toRead - count);
encoder_->encode(chunk.get()); encoder_->encode(chunk.get());
if (!active_) break; if (!active_)
break;
nextTick += pcmReadMs_; nextTick += pcmReadMs_;
chronos::addUs(tvChunk, pcmReadMs_ * 1000); chronos::addUs(tvChunk, pcmReadMs_ * 1000);
long currentTick = chronos::getTickCount(); long currentTick = chronos::getTickCount();
if (nextTick >= currentTick) if (nextTick >= currentTick)
{ {
// LOG(INFO) << "sleep: " << nextTick - currentTick << "\n"; // LOG(INFO) << "sleep: " << nextTick - currentTick << "\n";
if (!sleep(nextTick - currentTick)) if (!sleep(nextTick - currentTick))
break; break;
} }
@ -104,7 +104,7 @@ void FileStream::worker()
} }
} }
} }
catch(const std::exception& e) catch (const std::exception& e)
{ {
LOG(ERROR) << "(FileStream) Exception: " << e.what() << std::endl; LOG(ERROR) << "(FileStream) Exception: " << e.what() << std::endl;
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,23 +16,22 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <fcntl.h>
#include <memory> #include <memory>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h>
#include "encoder/encoderFactory.h" #include "aixlog.hpp"
#include "common/snapException.h" #include "common/snapException.h"
#include "common/strCompat.h" #include "common/strCompat.h"
#include "encoder/encoderFactory.h"
#include "pcmStream.h" #include "pcmStream.h"
#include "aixlog.hpp"
using namespace std; using namespace std;
PcmStream::PcmStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream::PcmStream(PcmListener* pcmListener, const StreamUri& uri) : active_(false), pcmListener_(pcmListener), uri_(uri), pcmReadMs_(20), state_(kIdle)
active_(false), pcmListener_(pcmListener), uri_(uri), pcmReadMs_(20), state_(kIdle)
{ {
EncoderFactory encoderFactory; EncoderFactory encoderFactory;
if (uri_.query.find("codec") == uri_.query.end()) if (uri_.query.find("codec") == uri_.query.end())
@ -56,8 +55,8 @@ PcmStream::PcmStream(PcmListener* pcmListener, const StreamUri& uri) :
else else
dryoutMs_ = 2000; dryoutMs_ = 2000;
//meta_.reset(new msg::StreamTags()); // meta_.reset(new msg::StreamTags());
//meta_->msg["stream"] = name_; // meta_->msg["stream"] = name_;
setMeta(json()); setMeta(json());
} }
@ -147,7 +146,7 @@ void PcmStream::setState(const ReaderState& newState)
void PcmStream::onChunkEncoded(const Encoder* encoder, msg::PcmChunk* chunk, double duration) void PcmStream::onChunkEncoded(const Encoder* encoder, msg::PcmChunk* chunk, double duration)
{ {
// LOG(INFO) << "onChunkEncoded: " << duration << " us\n"; // LOG(INFO) << "onChunkEncoded: " << duration << " us\n";
if (duration <= 0) if (duration <= 0)
return; return;
@ -170,12 +169,10 @@ json PcmStream::toJson() const
state = "disabled"; state = "disabled";
json j = { json j = {
{"uri", uri_.toJson()}, {"uri", uri_.toJson()}, {"id", getId()}, {"status", state},
{"id", getId()},
{"status", state},
}; };
if(meta_) if (meta_)
j["meta"] = meta_->msg; j["meta"] = meta_->msg;
return j; return j;
@ -196,4 +193,3 @@ void PcmStream::setMeta(json jtag)
if (pcmListener_) if (pcmListener_)
pcmListener_->onMetaChanged(this); pcmListener_->onMetaChanged(this);
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,18 +19,18 @@
#ifndef PCM_STREAM_H #ifndef PCM_STREAM_H
#define PCM_STREAM_H #define PCM_STREAM_H
#include <thread>
#include <atomic>
#include <string>
#include <mutex>
#include <condition_variable>
#include <map>
#include "streamUri.h"
#include "encoder/encoder.h"
#include "common/sampleFormat.h"
#include "common/json.hpp" #include "common/json.hpp"
#include "common/sampleFormat.h"
#include "encoder/encoder.h"
#include "message/codecHeader.h" #include "message/codecHeader.h"
#include "message/streamTags.h" #include "message/streamTags.h"
#include "streamUri.h"
#include <atomic>
#include <condition_variable>
#include <map>
#include <mutex>
#include <string>
#include <thread>
class PcmStream; class PcmStream;

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,24 +16,23 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <cerrno>
#include <fcntl.h>
#include <memory> #include <memory>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <cerrno>
#include "pipeStream.h" #include "aixlog.hpp"
#include "encoder/encoderFactory.h"
#include "common/snapException.h" #include "common/snapException.h"
#include "common/strCompat.h" #include "common/strCompat.h"
#include "aixlog.hpp" #include "encoder/encoderFactory.h"
#include "pipeStream.h"
using namespace std; using namespace std;
PipeStream::PipeStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), fd_(-1) PipeStream::PipeStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), fd_(-1)
{ {
umask(0); umask(0);
@ -73,7 +72,7 @@ void PipeStream::worker()
tvEncodedChunk_ = tvChunk; tvEncodedChunk_ = tvChunk;
long nextTick = chronos::getTickCount(); long nextTick = chronos::getTickCount();
int idleBytes = 0; int idleBytes = 0;
int maxIdleBytes = sampleFormat_.rate*sampleFormat_.frameSize*dryoutMs_/1000; int maxIdleBytes = sampleFormat_.rate * sampleFormat_.frameSize * dryoutMs_ / 1000;
try try
{ {
if (fd_ == -1) if (fd_ == -1)
@ -108,15 +107,16 @@ void PipeStream::worker()
len += count; len += count;
idleBytes = 0; idleBytes = 0;
} }
} } while ((len < toRead) && active_);
while ((len < toRead) && active_);
if (!active_) break; if (!active_)
break;
/// TODO: use less raw pointers, make this encoding more transparent /// TODO: use less raw pointers, make this encoding more transparent
encoder_->encode(chunk.get()); encoder_->encode(chunk.get());
if (!active_) break; if (!active_)
break;
nextTick += pcmReadMs_; nextTick += pcmReadMs_;
chronos::addUs(tvChunk, pcmReadMs_ * 1000); chronos::addUs(tvChunk, pcmReadMs_ * 1000);
@ -139,7 +139,7 @@ void PipeStream::worker()
lastException = ""; lastException = "";
} }
} }
catch(const std::exception& e) catch (const std::exception& e)
{ {
if (lastException != e.what()) if (lastException != e.what())
{ {

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -17,21 +17,20 @@
***/ ***/
#include <sys/stat.h>
#include <limits.h>
#include <fcntl.h>
#include "processStream.h" #include "processStream.h"
#include "common/snapException.h"
#include "common/utils/string_utils.h"
#include "common/utils.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/snapException.h"
#include "common/utils.h"
#include "common/utils/string_utils.h"
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>
using namespace std; using namespace std;
ProcessStream::ProcessStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), path_(""), process_(nullptr) ProcessStream::ProcessStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), path_(""), process_(nullptr)
{ {
params_ = uri_.getQuery("params"); params_ = uri_.getQuery("params");
@ -72,7 +71,7 @@ std::string ProcessStream::findExe(const std::string& filename)
char buff[PATH_MAX]; char buff[PATH_MAX];
char szTmp[32]; char szTmp[32];
sprintf(szTmp, "/proc/%d/exe", getpid()); sprintf(szTmp, "/proc/%d/exe", getpid());
ssize_t len = readlink(szTmp, buff, sizeof(buff)-1); ssize_t len = readlink(szTmp, buff, sizeof(buff) - 1);
if (len != -1) if (len != -1)
{ {
buff[len] = '\0'; buff[len] = '\0';
@ -134,7 +133,7 @@ void ProcessStream::stderrReader()
auto buffer = std::unique_ptr<char[]>(new char[buffer_size]); auto buffer = std::unique_ptr<char[]>(new char[buffer_size]);
ssize_t n; ssize_t n;
stringstream message; stringstream message;
while (active_ && (n=read(process_->getStderr(), buffer.get(), buffer_size)) > 0) while (active_ && (n = read(process_->getStderr(), buffer.get(), buffer_size)) > 0)
onStderrMsg(buffer.get(), n); onStderrMsg(buffer.get(), n);
} }
@ -159,7 +158,7 @@ void ProcessStream::worker()
tvEncodedChunk_ = tvChunk; tvEncodedChunk_ = tvChunk;
long nextTick = chronos::getTickCount(); long nextTick = chronos::getTickCount();
int idleBytes = 0; int idleBytes = 0;
int maxIdleBytes = sampleFormat_.rate*sampleFormat_.frameSize*dryoutMs_/1000; int maxIdleBytes = sampleFormat_.rate * sampleFormat_.frameSize * dryoutMs_ / 1000;
try try
{ {
while (active_) while (active_)
@ -191,14 +190,15 @@ void ProcessStream::worker()
len += count; len += count;
idleBytes = 0; idleBytes = 0;
} }
} } while ((len < toRead) && active_);
while ((len < toRead) && active_);
if (!active_) break; if (!active_)
break;
encoder_->encode(chunk.get()); encoder_->encode(chunk.get());
if (!active_) break; if (!active_)
break;
nextTick += pcmReadMs_; nextTick += pcmReadMs_;
chronos::addUs(tvChunk, pcmReadMs_ * 1000); chronos::addUs(tvChunk, pcmReadMs_ * 1000);
@ -221,7 +221,7 @@ void ProcessStream::worker()
lastException = ""; lastException = "";
} }
} }
catch(const std::exception& e) catch (const std::exception& e)
{ {
if (lastException != e.what()) if (lastException != e.what())
{ {

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,19 +16,18 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#include <regex>
#include "spotifyStream.h" #include "spotifyStream.h"
#include "common/snapException.h"
#include "common/utils/string_utils.h"
#include "common/utils.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/snapException.h"
#include "common/utils.h"
#include "common/utils/string_utils.h"
#include <regex>
using namespace std; using namespace std;
SpotifyStream::SpotifyStream(PcmListener* pcmListener, const StreamUri& uri) : ProcessStream(pcmListener, uri) SpotifyStream::SpotifyStream(PcmListener* pcmListener, const StreamUri& uri) : ProcessStream(pcmListener, uri)
{ {
sampleFormat_ = SampleFormat("44100:16:2"); sampleFormat_ = SampleFormat("44100:16:2");
@ -60,7 +59,7 @@ SpotifyStream::SpotifyStream(PcmListener* pcmListener, const StreamUri& uri) : P
uri_.query["username"] = "xxx"; uri_.query["username"] = "xxx";
if (uri_.query.find("password") != uri_.query.end()) if (uri_.query.find("password") != uri_.query.end())
uri_.query["password"] = "xxx"; uri_.query["password"] = "xxx";
// LOG(INFO) << "params: " << params << "\n"; // LOG(INFO) << "params: " << params << "\n";
} }
@ -119,9 +118,7 @@ void SpotifyStream::onStderrMsg(const char* buffer, size_t n)
watchdog_->trigger(); watchdog_->trigger();
string logmsg = utils::string::trim_copy(string(buffer, n)); string logmsg = utils::string::trim_copy(string(buffer, n));
if ((logmsg.find("allocated stream") == string::npos) && if ((logmsg.find("allocated stream") == string::npos) && (logmsg.find("Got channel") == string::npos) && (logmsg.find('\0') == string::npos) &&
(logmsg.find("Got channel") == string::npos) &&
(logmsg.find('\0') == string::npos) &&
(logmsg.size() > 4)) (logmsg.size() > 4))
{ {
LOG(INFO) << "(" << getName() << ") " << logmsg << "\n"; LOG(INFO) << "(" << getName() << ") " << logmsg << "\n";
@ -137,13 +134,11 @@ void SpotifyStream::onStderrMsg(const char* buffer, size_t n)
if (!libreelec_patched) if (!libreelec_patched)
{ {
static regex re_nonpatched("Track \"(.*)\" loaded"); static regex re_nonpatched("Track \"(.*)\" loaded");
if(regex_search(logmsg, m, re_nonpatched)) if (regex_search(logmsg, m, re_nonpatched))
{ {
LOG(INFO) << "metadata: <" << m[1] << ">\n"; LOG(INFO) << "metadata: <" << m[1] << ">\n";
json jtag = { json jtag = {{"TITLE", (string)m[1]}};
{"TITLE", (string)m[1]}
};
setMeta(jtag); setMeta(jtag);
} }
} }
@ -164,7 +159,7 @@ void SpotifyStream::stderrReader()
{ {
watchdog_.reset(new Watchdog(this)); watchdog_.reset(new Watchdog(this));
/// 130min /// 130min
watchdog_->start(130*60*1000); watchdog_->start(130 * 60 * 1000);
ProcessStream::stderrReader(); ProcessStream::stderrReader();
} }
@ -175,5 +170,3 @@ void SpotifyStream::onTimeout(const Watchdog* watchdog, size_t ms)
if (process_) if (process_)
process_->kill(); process_->kill();
} }

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -28,7 +28,8 @@
* Implements EncoderListener to get the encoded data. * Implements EncoderListener to get the encoded data.
* Data is passed to the PcmListener * Data is passed to the PcmListener
* usage: * usage:
* snapserver -s "spotify:///librespot?name=Spotify&username=<my username>&password=<my password>[&devicename=Snapcast][&bitrate=320][&volume=<volume in percent>][&cache=<cache dir>]" * snapserver -s "spotify:///librespot?name=Spotify&username=<my username>&password=<my password>[&devicename=Snapcast][&bitrate=320][&volume=<volume in
* percent>][&cache=<cache dir>]"
*/ */
class SpotifyStream : public ProcessStream, WatchdogListener class SpotifyStream : public ProcessStream, WatchdogListener
{ {

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2018 Johannes Pohl Copyright (C) 2014-2019 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -18,20 +18,21 @@
#include "streamManager.h" #include "streamManager.h"
#include "airplayStream.h" #include "airplayStream.h"
#include "spotifyStream.h"
#include "processStream.h"
#include "pipeStream.h"
#include "fileStream.h"
#include "common/utils.h"
#include "common/strCompat.h"
#include "aixlog.hpp" #include "aixlog.hpp"
#include "common/snapException.h" #include "common/snapException.h"
#include "common/strCompat.h"
#include "common/utils.h"
#include "fileStream.h"
#include "pipeStream.h"
#include "processStream.h"
#include "spotifyStream.h"
using namespace std; using namespace std;
StreamManager::StreamManager(PcmListener* pcmListener, const std::string& defaultSampleFormat, const std::string& defaultCodec, size_t defaultReadBufferMs) : pcmListener_(pcmListener), sampleFormat_(defaultSampleFormat), codec_(defaultCodec), readBufferMs_(defaultReadBufferMs) StreamManager::StreamManager(PcmListener* pcmListener, const std::string& defaultSampleFormat, const std::string& defaultCodec, size_t defaultReadBufferMs)
: pcmListener_(pcmListener), sampleFormat_(defaultSampleFormat), codec_(defaultCodec), readBufferMs_(defaultReadBufferMs)
{ {
} }
@ -49,11 +50,11 @@ PcmStreamPtr StreamManager::addStream(const std::string& uri)
if (streamUri.query.find("buffer_ms") == streamUri.query.end()) if (streamUri.query.find("buffer_ms") == streamUri.query.end())
streamUri.query["buffer_ms"] = cpt::to_string(readBufferMs_); streamUri.query["buffer_ms"] = cpt::to_string(readBufferMs_);
// LOG(DEBUG) << "\nURI: " << streamUri.uri << "\nscheme: " << streamUri.scheme << "\nhost: " // LOG(DEBUG) << "\nURI: " << streamUri.uri << "\nscheme: " << streamUri.scheme << "\nhost: "
// << streamUri.host << "\npath: " << streamUri.path << "\nfragment: " << streamUri.fragment << "\n"; // << streamUri.host << "\npath: " << streamUri.path << "\nfragment: " << streamUri.fragment << "\n";
// for (auto kv: streamUri.query) // for (auto kv: streamUri.query)
// LOG(DEBUG) << "key: '" << kv.first << "' value: '" << kv.second << "'\n"; // LOG(DEBUG) << "key: '" << kv.first << "' value: '" << kv.second << "'\n";
PcmStreamPtr stream(nullptr); PcmStreamPtr stream(nullptr);
if (streamUri.scheme == "pipe") if (streamUri.scheme == "pipe")
@ -83,7 +84,7 @@ PcmStreamPtr StreamManager::addStream(const std::string& uri)
if (stream) if (stream)
{ {
for (auto s: streams_) for (auto s : streams_)
{ {
if (s->getName() == stream->getName()) if (s->getName() == stream->getName())
throw SnapException("Stream with name \"" + stream->getName() + "\" already exists"); throw SnapException("Stream with name \"" + stream->getName() + "\" already exists");
@ -112,7 +113,7 @@ const PcmStreamPtr StreamManager::getDefaultStream()
const PcmStreamPtr StreamManager::getStream(const std::string& id) const PcmStreamPtr StreamManager::getStream(const std::string& id)
{ {
for (auto stream: streams_) for (auto stream : streams_)
{ {
if (stream->getId() == id) if (stream->getId() == id)
return stream; return stream;
@ -123,14 +124,14 @@ const PcmStreamPtr StreamManager::getStream(const std::string& id)
void StreamManager::start() void StreamManager::start()
{ {
for (auto stream: streams_) for (auto stream : streams_)
stream->start(); stream->start();
} }
void StreamManager::stop() void StreamManager::stop()
{ {
for (auto stream: streams_) for (auto stream : streams_)
stream->stop(); stream->stop();
} }
@ -138,9 +139,7 @@ void StreamManager::stop()
json StreamManager::toJson() const json StreamManager::toJson() const
{ {
json result = json::array(); json result = json::array();
for (auto stream: streams_) for (auto stream : streams_)
result.push_back(stream->toJson()); result.push_back(stream->toJson());
return result; return result;
} }

View file

@ -1,10 +1,10 @@
#ifndef PCM_READER_FACTORY_H #ifndef PCM_READER_FACTORY_H
#define PCM_READER_FACTORY_H #define PCM_READER_FACTORY_H
#include "pcmStream.h"
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
#include "pcmStream.h"
typedef std::shared_ptr<PcmStream> PcmStreamPtr; typedef std::shared_ptr<PcmStream> PcmStreamPtr;

Some files were not shown because too many files have changed in this diff Show more