diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..7b1bec3d --- /dev/null +++ b/.clang-format @@ -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 + +... diff --git a/client/Makefile b/client/Makefile index baf21468..2a5a1712 100644 --- a/client/Makefile +++ b/client/Makefile @@ -1,5 +1,5 @@ # 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 # it under the terms of the GNU General Public License as published by diff --git a/client/browseZeroConf/browseAvahi.cpp b/client/browseZeroConf/browseAvahi.cpp old mode 100755 new mode 100644 index 9e964957..78084909 --- a/client/browseZeroConf/browseAvahi.cpp +++ b/client/browseZeroConf/browseAvahi.cpp @@ -18,16 +18,16 @@ ***/ #include "browseAvahi.h" -#include +#include "aixlog.hpp" +#include "common/snapException.h" #include +#include +#include #include #include -#include -#include "common/snapException.h" -#include "aixlog.hpp" -static AvahiSimplePoll *simple_poll = NULL; +static AvahiSimplePoll* simple_poll = NULL; BrowseAvahi::BrowseAvahi() : client_(NULL), sb_(NULL) @@ -37,188 +37,171 @@ BrowseAvahi::BrowseAvahi() : client_(NULL), sb_(NULL) BrowseAvahi::~BrowseAvahi() { - cleanUp(); + cleanUp(); } void BrowseAvahi::cleanUp() { - if (sb_) - avahi_service_browser_free(sb_); - sb_ = NULL; + if (sb_) + avahi_service_browser_free(sb_); + sb_ = NULL; - if (client_) - avahi_client_free(client_); - client_ = NULL; + if (client_) + avahi_client_free(client_); + client_ = NULL; - if (simple_poll) - avahi_simple_poll_free(simple_poll); - simple_poll = NULL; + if (simple_poll) + avahi_simple_poll_free(simple_poll); + simple_poll = NULL; } -void BrowseAvahi::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) +void BrowseAvahi::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) { - BrowseAvahi* browseAvahi = static_cast(userdata); - assert(r); + BrowseAvahi* browseAvahi = static_cast(userdata); + assert(r); - /* Called whenever a service has been resolved successfully or timed out */ + /* Called whenever a service has been resolved successfully or timed out */ - switch (event) - { - 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"; - break; + switch (event) + { + 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"; + break; - case AVAHI_RESOLVER_FOUND: - { - char a[AVAHI_ADDRESS_STR_MAX], *t; + case AVAHI_RESOLVER_FOUND: + { + char a[AVAHI_ADDRESS_STR_MAX], *t; - LOG(INFO) << "Service '" << name << "' of type '" << type << "' in domain '" << domain << "':\n"; + LOG(INFO) << "Service '" << name << "' of type '" << type << "' in domain '" << domain << "':\n"; - avahi_address_snprint(a, sizeof(a), address); - browseAvahi->result_.host = host_name; - browseAvahi->result_.ip = a; - browseAvahi->result_.port = port; - // 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_.valid = true; - browseAvahi->result_.iface_idx = interface; + avahi_address_snprint(a, sizeof(a), address); + browseAvahi->result_.host = host_name; + browseAvahi->result_.ip = a; + browseAvahi->result_.port = port; + // 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_.valid = true; + browseAvahi->result_.iface_idx = interface; - t = avahi_string_list_to_string(txt); - LOG(INFO) << "\t" << host_name << ":" << port << " (" << a << ")\n"; - LOG(DEBUG) << "\tTXT=" << t << "\n"; - LOG(DEBUG) << "\tProto=" << (int)protocol << "\n"; - LOG(DEBUG) << "\tcookie is " << avahi_string_list_get_service_cookie(txt) << "\n"; - LOG(DEBUG) << "\tis_local: " << !!(flags & AVAHI_LOOKUP_RESULT_LOCAL) << "\n"; - LOG(DEBUG) << "\tour_own: " << !!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN) << "\n"; - LOG(DEBUG) << "\twide_area: " << !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA) << "\n"; - LOG(DEBUG) << "\tmulticast: " << !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST) << "\n"; - LOG(DEBUG) << "\tcached: " << !!(flags & AVAHI_LOOKUP_RESULT_CACHED) << "\n"; - avahi_free(t); - } - } + t = avahi_string_list_to_string(txt); + LOG(INFO) << "\t" << host_name << ":" << port << " (" << a << ")\n"; + LOG(DEBUG) << "\tTXT=" << t << "\n"; + LOG(DEBUG) << "\tProto=" << (int)protocol << "\n"; + LOG(DEBUG) << "\tcookie is " << avahi_string_list_get_service_cookie(txt) << "\n"; + LOG(DEBUG) << "\tis_local: " << !!(flags & AVAHI_LOOKUP_RESULT_LOCAL) << "\n"; + LOG(DEBUG) << "\tour_own: " << !!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN) << "\n"; + LOG(DEBUG) << "\twide_area: " << !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA) << "\n"; + LOG(DEBUG) << "\tmulticast: " << !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST) << "\n"; + LOG(DEBUG) << "\tcached: " << !!(flags & AVAHI_LOOKUP_RESULT_CACHED) << "\n"; + avahi_free(t); + } + } - avahi_service_resolver_free(r); + avahi_service_resolver_free(r); } -void BrowseAvahi::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) +void BrowseAvahi::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) { -// AvahiClient* client = (AvahiClient*)userdata; - BrowseAvahi* browseAvahi = static_cast(userdata); - assert(b); + // AvahiClient* client = (AvahiClient*)userdata; + BrowseAvahi* browseAvahi = static_cast(userdata); + assert(b); - /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ + /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ - switch (event) - { - case AVAHI_BROWSER_FAILURE: - LOG(ERROR) << "(Browser) " << avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))) << "\n"; - avahi_simple_poll_quit(simple_poll); - return; + switch (event) + { + case AVAHI_BROWSER_FAILURE: + LOG(ERROR) << "(Browser) " << avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))) << "\n"; + avahi_simple_poll_quit(simple_poll); + return; - case AVAHI_BROWSER_NEW: - LOG(INFO) << "(Browser) NEW: service '" << name << "' of type '" << type << "' in domain '" << domain << "'\n"; + case AVAHI_BROWSER_NEW: + LOG(INFO) << "(Browser) NEW: service '" << name << "' of type '" << type << "' in domain '" << domain << "'\n"; - /* We ignore the returned resolver object. In the callback - function we free it. If the server is terminated before - the callback function is called the server will free - the resolver for us. */ + /* We ignore the returned resolver object. In the callback + function we free it. If the server is terminated before + the callback function is called the server will free + the resolver for us. */ - 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"; + 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"; - break; + break; - case AVAHI_BROWSER_REMOVE: - LOG(INFO) << "(Browser) REMOVE: service '" << name << "' of type '" << type << "' in domain '" << domain << "'\n"; - break; + case AVAHI_BROWSER_REMOVE: + LOG(INFO) << "(Browser) REMOVE: service '" << name << "' of type '" << type << "' in domain '" << domain << "'\n"; + break; - case AVAHI_BROWSER_ALL_FOR_NOW: - case AVAHI_BROWSER_CACHE_EXHAUSTED: - LOG(INFO) << "(Browser) " << (event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW") << "\n"; - break; - } + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + LOG(INFO) << "(Browser) " << (event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW") << "\n"; + break; + } } -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 */ -// BrowseAvahi* browseAvahi = static_cast(userdata); + /* Called whenever the client or server state changes */ + // BrowseAvahi* browseAvahi = static_cast(userdata); - if (state == AVAHI_CLIENT_FAILURE) - { - LOG(ERROR) << "Server connection failure: " << avahi_strerror(avahi_client_errno(c)) << "\n"; - avahi_simple_poll_quit(simple_poll); - } + if (state == AVAHI_CLIENT_FAILURE) + { + LOG(ERROR) << "Server connection failure: " << avahi_strerror(avahi_client_errno(c)) << "\n"; + avahi_simple_poll_quit(simple_poll); + } } bool BrowseAvahi::browse(const std::string& serviceName, mDNSResult& result, int timeout) { - try - { - /* Allocate main loop object */ - if (!(simple_poll = avahi_simple_poll_new())) - throw SnapException("BrowseAvahi - Failed to create simple poll object"); + try + { + /* Allocate main loop object */ + if (!(simple_poll = avahi_simple_poll_new())) + throw SnapException("BrowseAvahi - Failed to create simple poll object"); - /* Allocate a new client */ - int error; - if (!(client_ = avahi_client_new(avahi_simple_poll_get(simple_poll), (AvahiClientFlags)0, client_callback, this, &error))) - throw SnapException("BrowseAvahi - Failed to create client: " + std::string(avahi_strerror(error))); + /* Allocate a new client */ + int error; + if (!(client_ = avahi_client_new(avahi_simple_poll_get(simple_poll), (AvahiClientFlags)0, client_callback, this, &error))) + throw SnapException("BrowseAvahi - Failed to create client: " + std::string(avahi_strerror(error))); - /* 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))) - throw SnapException("BrowseAvahi - Failed to create service browser: " + std::string(avahi_strerror(avahi_client_errno(client_)))); + /* 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))) + throw SnapException("BrowseAvahi - Failed to create service browser: " + std::string(avahi_strerror(avahi_client_errno(client_)))); - result_.valid = false; - while (timeout > 0) - { - avahi_simple_poll_iterate(simple_poll, 100); - timeout -= 100; - if (result_.valid) - { - result = result_; - cleanUp(); - return true; - } - } + result_.valid = false; + while (timeout > 0) + { + avahi_simple_poll_iterate(simple_poll, 100); + timeout -= 100; + if (result_.valid) + { + result = result_; + cleanUp(); + return true; + } + } - cleanUp(); - return false; - } - catch (...) - { - cleanUp(); - throw; - } + cleanUp(); + return false; + } + catch (...) + { + cleanUp(); + throw; + } } - - diff --git a/client/browseZeroConf/browseAvahi.h b/client/browseZeroConf/browseAvahi.h old mode 100755 new mode 100644 index acfdf3de..900f4c59 --- a/client/browseZeroConf/browseAvahi.h +++ b/client/browseZeroConf/browseAvahi.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -21,9 +21,9 @@ #include #include -#include -#include #include +#include +#include class BrowseAvahi; @@ -32,18 +32,22 @@ class BrowseAvahi; class BrowseAvahi : public BrowsemDNS { public: - BrowseAvahi(); - ~BrowseAvahi(); - bool browse(const std::string& serviceName, mDNSResult& result, int timeout) override; + BrowseAvahi(); + ~BrowseAvahi(); + bool browse(const std::string& serviceName, mDNSResult& result, int timeout) override; private: - 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 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_; - mDNSResult result_; - AvahiServiceBrowser* sb_; + 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 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_; + mDNSResult result_; + AvahiServiceBrowser* sb_; }; #endif diff --git a/client/browseZeroConf/browseBonjour.cpp b/client/browseZeroConf/browseBonjour.cpp old mode 100755 new mode 100644 index c173679c..4b25dda0 --- a/client/browseZeroConf/browseBonjour.cpp +++ b/client/browseZeroConf/browseBonjour.cpp @@ -1,14 +1,14 @@ #include "browseBonjour.h" -#include -#include #include +#include +#include #ifdef WINDOWS #include #include #else -#include #include +#include #endif #include "aixlog.hpp" @@ -18,253 +18,234 @@ using namespace std; struct DNSServiceRefDeleter { - void operator () (DNSServiceRef* ref) - { - DNSServiceRefDeallocate(*ref); - delete ref; - } + void operator()(DNSServiceRef* ref) + { + DNSServiceRefDeallocate(*ref); + delete ref; + } }; typedef std::unique_ptr DNSServiceHandle; string BonjourGetError(DNSServiceErrorType error) { - switch (error) - { - case kDNSServiceErr_NoError: - return "NoError"; + switch (error) + { + case kDNSServiceErr_NoError: + return "NoError"; - default: - case kDNSServiceErr_Unknown: - return "Unknown"; - - case kDNSServiceErr_NoSuchName: - return "NoSuchName"; - - case kDNSServiceErr_NoMemory: - return "NoMemory"; - - case kDNSServiceErr_BadParam: - return "BadParam"; - - case kDNSServiceErr_BadReference: - return "BadReference"; - - case kDNSServiceErr_BadState: - return "BadState"; - - case kDNSServiceErr_BadFlags: - return "BadFlags"; - - case kDNSServiceErr_Unsupported: - return "Unsupported"; - - case kDNSServiceErr_NotInitialized: - return "NotInitialized"; - - case kDNSServiceErr_AlreadyRegistered: - return "AlreadyRegistered"; - - case kDNSServiceErr_NameConflict: - return "NameConflict"; - - case kDNSServiceErr_Invalid: - return "Invalid"; - - case kDNSServiceErr_Firewall: - return "Firewall"; - - case kDNSServiceErr_Incompatible: - return "Incompatible"; - - case kDNSServiceErr_BadInterfaceIndex: - return "BadInterfaceIndex"; - - case kDNSServiceErr_Refused: - return "Refused"; - - case kDNSServiceErr_NoSuchRecord: - return "NoSuchRecord"; - - case kDNSServiceErr_NoAuth: - return "NoAuth"; - - case kDNSServiceErr_NoSuchKey: - return "NoSuchKey"; - - case kDNSServiceErr_NATTraversal: - return "NATTraversal"; - - case kDNSServiceErr_DoubleNAT: - return "DoubleNAT"; - - case kDNSServiceErr_BadTime: - return "BadTime"; - - case kDNSServiceErr_BadSig: - return "BadSig"; - - case kDNSServiceErr_BadKey: - return "BadKey"; - - case kDNSServiceErr_Transient: - return "Transient"; - - case kDNSServiceErr_ServiceNotRunning: - return "ServiceNotRunning"; - - case kDNSServiceErr_NATPortMappingUnsupported: - return "NATPortMappingUnsupported"; - - case kDNSServiceErr_NATPortMappingDisabled: - return "NATPortMappingDisabled"; - - case kDNSServiceErr_NoRouter: - return "NoRouter"; - - case kDNSServiceErr_PollingMode: - return "PollingMode"; - - case kDNSServiceErr_Timeout: - return "Timeout"; - } + default: + case kDNSServiceErr_Unknown: + return "Unknown"; + + case kDNSServiceErr_NoSuchName: + return "NoSuchName"; + + case kDNSServiceErr_NoMemory: + return "NoMemory"; + + case kDNSServiceErr_BadParam: + return "BadParam"; + + case kDNSServiceErr_BadReference: + return "BadReference"; + + case kDNSServiceErr_BadState: + return "BadState"; + + case kDNSServiceErr_BadFlags: + return "BadFlags"; + + case kDNSServiceErr_Unsupported: + return "Unsupported"; + + case kDNSServiceErr_NotInitialized: + return "NotInitialized"; + + case kDNSServiceErr_AlreadyRegistered: + return "AlreadyRegistered"; + + case kDNSServiceErr_NameConflict: + return "NameConflict"; + + case kDNSServiceErr_Invalid: + return "Invalid"; + + case kDNSServiceErr_Firewall: + return "Firewall"; + + case kDNSServiceErr_Incompatible: + return "Incompatible"; + + case kDNSServiceErr_BadInterfaceIndex: + return "BadInterfaceIndex"; + + case kDNSServiceErr_Refused: + return "Refused"; + + case kDNSServiceErr_NoSuchRecord: + return "NoSuchRecord"; + + case kDNSServiceErr_NoAuth: + return "NoAuth"; + + case kDNSServiceErr_NoSuchKey: + return "NoSuchKey"; + + case kDNSServiceErr_NATTraversal: + return "NATTraversal"; + + case kDNSServiceErr_DoubleNAT: + return "DoubleNAT"; + + case kDNSServiceErr_BadTime: + return "BadTime"; + + case kDNSServiceErr_BadSig: + return "BadSig"; + + case kDNSServiceErr_BadKey: + return "BadKey"; + + case kDNSServiceErr_Transient: + return "Transient"; + + case kDNSServiceErr_ServiceNotRunning: + return "ServiceNotRunning"; + + case kDNSServiceErr_NATPortMappingUnsupported: + return "NATPortMappingUnsupported"; + + case kDNSServiceErr_NATPortMappingDisabled: + return "NATPortMappingDisabled"; + + case kDNSServiceErr_NoRouter: + return "NoRouter"; + + case kDNSServiceErr_PollingMode: + return "PollingMode"; + + case kDNSServiceErr_Timeout: + return "Timeout"; + } } struct mDNSReply { - string name, regtype, domain; + string name, regtype, domain; }; struct mDNSResolve { - string fullName; - uint16_t port; + string fullName; + 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) { - if (!*service) - return; + if (!*service) + return; - auto socket = DNSServiceRefSockFD(*service); - fd_set set; - FD_ZERO(&set); - FD_SET(socket, &set); - - timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 500000; - - while (select(FD_SETSIZE, &set, NULL, NULL, &timeout)) - { - CHECKED(DNSServiceProcessResult(*service)); - timeout.tv_sec = 0; - timeout.tv_usec = 500000; - } + auto socket = DNSServiceRefSockFD(*service); + fd_set set; + FD_ZERO(&set); + FD_SET(socket, &set); + + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 500000; + + while (select(FD_SETSIZE, &set, NULL, NULL, &timeout)) + { + CHECKED(DNSServiceProcessResult(*service)); + timeout.tv_sec = 0; + timeout.tv_usec = 500000; + } } bool BrowseBonjour::browse(const string& serviceName, mDNSResult& result, int timeout) { - result.valid = false; - // Discover - deque replyCollection; - { - DNSServiceHandle service(new DNSServiceRef(NULL)); - CHECKED(DNSServiceBrowse(service.get(), 0, 0, serviceName.c_str(), "local.", - [](DNSServiceRef service, - DNSServiceFlags flags, - uint32_t interfaceIndex, - DNSServiceErrorType errorCode, - const char* serviceName, - const char* regtype, - const char* replyDomain, - void* context) - { - auto replyCollection = static_cast*>(context); - - CHECKED(errorCode); - replyCollection->push_back(mDNSReply{ string(serviceName), string(regtype), string(replyDomain) }); - }, &replyCollection)); + result.valid = false; + // Discover + deque replyCollection; + { + DNSServiceHandle service(new DNSServiceRef(NULL)); + CHECKED(DNSServiceBrowse(service.get(), 0, 0, serviceName.c_str(), "local.", + [](DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, + const char* serviceName, const char* regtype, const char* replyDomain, void* context) { + auto replyCollection = static_cast*>(context); - runService(service); - } + CHECKED(errorCode); + replyCollection->push_back(mDNSReply{string(serviceName), string(regtype), string(replyDomain)}); + }, + &replyCollection)); - // Resolve - deque resolveCollection; - { - DNSServiceHandle service(new DNSServiceRef(NULL)); - for (auto& reply : replyCollection) - CHECKED(DNSServiceResolve(service.get(), 0, 0, reply.name.c_str(), reply.regtype.c_str(), reply.domain.c_str(), - [](DNSServiceRef service, - DNSServiceFlags flags, - uint32_t interfaceIndex, - DNSServiceErrorType errorCode, - const char* fullName, - const char* hosttarget, - uint16_t port, - uint16_t txtLen, - const unsigned char* txtRecord, - void* context) - { - auto resultCollection = static_cast*>(context); - - CHECKED(errorCode); - resultCollection->push_back(mDNSResolve { string(hosttarget), ntohs(port) }); - }, &resolveCollection)); + runService(service); + } - runService(service); - } + // Resolve + deque resolveCollection; + { + DNSServiceHandle service(new DNSServiceRef(NULL)); + for (auto& reply : replyCollection) + CHECKED( + DNSServiceResolve(service.get(), 0, 0, reply.name.c_str(), reply.regtype.c_str(), reply.domain.c_str(), + [](DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char* fullName, + const char* hosttarget, uint16_t port, uint16_t txtLen, const unsigned char* txtRecord, void* context) { + auto resultCollection = static_cast*>(context); - // DNS/mDNS Resolve - deque resultCollection(resolveCollection.size(), mDNSResult { IPVersion::IPv4, 0, "", "", 0, false }); - { - DNSServiceHandle service(new DNSServiceRef(NULL)); - unsigned i = 0; - for (auto& resolve : resolveCollection) - { - resultCollection[i].port = resolve.port; - CHECKED(DNSServiceGetAddrInfo(service.get(), kDNSServiceFlagsLongLivedQuery, 0, kDNSServiceProtocol_IPv4, resolve.fullName.c_str(), - [](DNSServiceRef service, - DNSServiceFlags flags, - uint32_t interfaceIndex, - DNSServiceErrorType errorCode, - const char* hostname, - const sockaddr* address, - uint32_t ttl, - void* context) - { - auto result = static_cast(context); + CHECKED(errorCode); + resultCollection->push_back(mDNSResolve{string(hosttarget), ntohs(port)}); + }, + &resolveCollection)); - result->host = string(hostname); - result->ip_version = (address->sa_family == AF_INET)?(IPVersion::IPv4):(IPVersion::IPv6); - result->iface_idx = static_cast(interfaceIndex); + runService(service); + } - char hostIP[NI_MAXHOST]; - char hostService[NI_MAXSERV]; - if (getnameinfo(address, sizeof(*address), - hostIP, sizeof(hostIP), - hostService, sizeof(hostService), - NI_NUMERICHOST|NI_NUMERICSERV) == 0) - result->ip = string(hostIP); - else - return; - result->valid = true; - }, &resultCollection[i++])); - } - runService(service); - } + // DNS/mDNS Resolve + deque resultCollection(resolveCollection.size(), mDNSResult{IPVersion::IPv4, 0, "", "", 0, false}); + { + DNSServiceHandle service(new DNSServiceRef(NULL)); + unsigned i = 0; + for (auto& resolve : resolveCollection) + { + resultCollection[i].port = resolve.port; + CHECKED(DNSServiceGetAddrInfo(service.get(), kDNSServiceFlagsLongLivedQuery, 0, kDNSServiceProtocol_IPv4, resolve.fullName.c_str(), + [](DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, + const char* hostname, const sockaddr* address, uint32_t ttl, void* context) { + auto result = static_cast(context); - if (resultCollection.size() == 0) - return false; + result->host = string(hostname); + result->ip_version = (address->sa_family == AF_INET) ? (IPVersion::IPv4) : (IPVersion::IPv6); + result->iface_idx = static_cast(interfaceIndex); - if (resultCollection.size() != 1) - LOG(NOTICE) << "Multiple servers found. Using first" << endl; + char hostIP[NI_MAXHOST]; + char hostService[NI_MAXSERV]; + if (getnameinfo(address, sizeof(*address), hostIP, sizeof(hostIP), hostService, sizeof(hostService), + NI_NUMERICHOST | NI_NUMERICSERV) == 0) + result->ip = string(hostIP); + else + return; + result->valid = true; + }, + &resultCollection[i++])); + } + runService(service); + } - result = resultCollection[0]; - - return true; + if (resultCollection.size() == 0) + return false; + + if (resultCollection.size() != 1) + LOG(NOTICE) << "Multiple servers found. Using first" << endl; + + result = resultCollection[0]; + + return true; } #undef CHECKED diff --git a/client/browseZeroConf/browseBonjour.h b/client/browseZeroConf/browseBonjour.h old mode 100755 new mode 100644 index 64dd3b4f..57d2347a --- a/client/browseZeroConf/browseBonjour.h +++ b/client/browseZeroConf/browseBonjour.h @@ -10,6 +10,6 @@ class BrowseBonjour; class BrowseBonjour : public BrowsemDNS { public: - bool browse(const std::string& serviceName, mDNSResult& result, int timeout) override; + bool browse(const std::string& serviceName, mDNSResult& result, int timeout) override; }; #endif diff --git a/client/browseZeroConf/browsemDNS.h b/client/browseZeroConf/browsemDNS.h old mode 100755 new mode 100644 index bee88999..4d06c005 --- a/client/browseZeroConf/browsemDNS.h +++ b/client/browseZeroConf/browsemDNS.h @@ -5,25 +5,25 @@ enum IPVersion { - IPv4 = 0, - IPv6 = 1 + IPv4 = 0, + IPv6 = 1 }; struct mDNSResult { - IPVersion ip_version; - int iface_idx; - std::string ip; - std::string host; - uint16_t port; - bool valid; + IPVersion ip_version; + int iface_idx; + std::string ip; + std::string host; + uint16_t port; + bool valid; }; class BrowsemDNS { public: - virtual bool browse(const std::string& serviceName, mDNSResult& result, int timeout) = 0; + virtual bool browse(const std::string& serviceName, mDNSResult& result, int timeout) = 0; }; #if defined(HAS_AVAHI) diff --git a/client/clientConnection.cpp b/client/clientConnection.cpp index caf0cf10..576d8ce2 100644 --- a/client/clientConnection.cpp +++ b/client/clientConnection.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,221 +16,222 @@ along with this program. If not, see . ***/ +#include "clientConnection.h" +#include "aixlog.hpp" +#include "common/snapException.h" +#include "common/strCompat.h" +#include "message/hello.h" #include #include -#include "clientConnection.h" -#include "common/strCompat.h" -#include "common/snapException.h" -#include "message/hello.h" -#include "aixlog.hpp" 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)) { } ClientConnection::~ClientConnection() { - stop(); + stop(); } void ClientConnection::socketRead(void* _to, size_t _bytes) { - size_t toRead = _bytes; - size_t len = 0; - do - { - len += socket_->read_some(asio::buffer((char*)_to + len, toRead)); -//cout << "len: " << len << ", error: " << error << endl; - toRead = _bytes - len; - } - while (toRead > 0); + size_t toRead = _bytes; + size_t len = 0; + do + { + len += socket_->read_some(asio::buffer((char*)_to + len, toRead)); + // cout << "len: " << len << ", error: " << error << endl; + toRead = _bytes - len; + } while (toRead > 0); } std::string ClientConnection::getMacAddress() const { - if (socket_ == nullptr) - throw SnapException("socket not connected"); + if (socket_ == nullptr) + throw SnapException("socket not connected"); - std::string mac = ::getMacAddress(socket_->native_handle()); - if (mac.empty()) - mac = "00:00:00:00:00:00"; - LOG(INFO) << "My MAC: \"" << mac << "\", socket: " << socket_->native_handle() << "\n"; - return mac; + std::string mac = ::getMacAddress(socket_->native_handle()); + if (mac.empty()) + mac = "00:00:00:00:00:00"; + LOG(INFO) << "My MAC: \"" << mac << "\", socket: " << socket_->native_handle() << "\n"; + return mac; } void ClientConnection::start() { - tcp::resolver resolver(io_service_); - tcp::resolver::query query(host_, cpt::to_string(port_), asio::ip::resolver_query_base::numeric_service); - auto iterator = resolver.resolve(query); - LOG(DEBUG) << "Connecting\n"; - socket_.reset(new tcp::socket(io_service_)); -// struct timeval tv; -// tv.tv_sec = 5; -// tv.tv_usec = 0; -// cout << "socket: " << socket->native_handle() << "\n"; -// setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); -// setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - socket_->connect(*iterator); - connected_ = true; - SLOG(NOTICE) << "Connected to " << socket_->remote_endpoint().address().to_string() << endl; - active_ = true; - sumTimeout_ = chronos::msec(0); - readerThread_ = new thread(&ClientConnection::reader, this); + tcp::resolver resolver(io_service_); + tcp::resolver::query query(host_, cpt::to_string(port_), asio::ip::resolver_query_base::numeric_service); + auto iterator = resolver.resolve(query); + LOG(DEBUG) << "Connecting\n"; + socket_.reset(new tcp::socket(io_service_)); + // struct timeval tv; + // tv.tv_sec = 5; + // tv.tv_usec = 0; + // cout << "socket: " << socket->native_handle() << "\n"; + // setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + // setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + socket_->connect(*iterator); + connected_ = true; + SLOG(NOTICE) << "Connected to " << socket_->remote_endpoint().address().to_string() << endl; + active_ = true; + sumTimeout_ = chronos::msec(0); + readerThread_ = new thread(&ClientConnection::reader, this); } void ClientConnection::stop() { - connected_ = false; - active_ = false; - try - { - std::error_code ec; - if (socket_) - { - socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); - if (ec) LOG(ERROR) << "Error in socket shutdown: " << ec.message() << endl; - socket_->close(ec); - if (ec) LOG(ERROR) << "Error in socket close: " << ec.message() << endl; - } - if (readerThread_) - { - LOG(DEBUG) << "joining readerThread\n"; - readerThread_->join(); - delete readerThread_; - } - } - catch(...) - { - } - readerThread_ = NULL; - socket_.reset(); - LOG(DEBUG) << "readerThread terminated\n"; + connected_ = false; + active_ = false; + try + { + std::error_code ec; + if (socket_) + { + socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); + if (ec) + LOG(ERROR) << "Error in socket shutdown: " << ec.message() << endl; + socket_->close(ec); + if (ec) + LOG(ERROR) << "Error in socket close: " << ec.message() << endl; + } + if (readerThread_) + { + LOG(DEBUG) << "joining readerThread\n"; + readerThread_->join(); + delete readerThread_; + } + } + catch (...) + { + } + readerThread_ = NULL; + socket_.reset(); + LOG(DEBUG) << "readerThread terminated\n"; } bool ClientConnection::send(const msg::BaseMessage* message) const { -// std::unique_lock mlock(mutex_); -//LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n"; - std::lock_guard socketLock(socketMutex_); - if (!connected()) - return false; -//LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n"; - asio::streambuf streambuf; - std::ostream stream(&streambuf); - tv t; - message->sent = t; - message->serialize(stream); - asio::write(*socket_.get(), streambuf); - return true; + // std::unique_lock mlock(mutex_); + // LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n"; + std::lock_guard socketLock(socketMutex_); + if (!connected()) + return false; + // LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n"; + asio::streambuf streambuf; + std::ostream stream(&streambuf); + tv t; + message->sent = t; + message->serialize(stream); + asio::write(*socket_.get(), streambuf); + return true; } shared_ptr ClientConnection::sendRequest(const msg::BaseMessage* message, const chronos::msec& timeout) { - shared_ptr response(NULL); - if (++reqId_ >= 10000) - reqId_ = 1; - message->id = reqId_; -// LOG(INFO) << "Req: " << message->id << "\n"; - shared_ptr pendingRequest(new PendingRequest(reqId_)); + shared_ptr response(NULL); + if (++reqId_ >= 10000) + reqId_ = 1; + message->id = reqId_; + // LOG(INFO) << "Req: " << message->id << "\n"; + shared_ptr pendingRequest(new PendingRequest(reqId_)); - std::unique_lock lock(pendingRequestsMutex_); - pendingRequests_.insert(pendingRequest); - send(message); - if (pendingRequest->cv.wait_for(lock, std::chrono::milliseconds(timeout)) == std::cv_status::no_timeout) - { - response = pendingRequest->response; - sumTimeout_ = chronos::msec(0); -// LOG(INFO) << "Resp: " << pendingRequest->id << "\n"; - } - else - { - sumTimeout_ += timeout; - LOG(WARNING) << "timeout while waiting for response to: " << reqId_ << ", timeout " << sumTimeout_.count() << "\n"; - if (sumTimeout_ > chronos::sec(10)) - throw SnapException("sum timeout exceeded 10s"); - } - pendingRequests_.erase(pendingRequest); - return response; + std::unique_lock lock(pendingRequestsMutex_); + pendingRequests_.insert(pendingRequest); + send(message); + if (pendingRequest->cv.wait_for(lock, std::chrono::milliseconds(timeout)) == std::cv_status::no_timeout) + { + response = pendingRequest->response; + sumTimeout_ = chronos::msec(0); + // LOG(INFO) << "Resp: " << pendingRequest->id << "\n"; + } + else + { + sumTimeout_ += timeout; + LOG(WARNING) << "timeout while waiting for response to: " << reqId_ << ", timeout " << sumTimeout_.count() << "\n"; + if (sumTimeout_ > chronos::sec(10)) + throw SnapException("sum timeout exceeded 10s"); + } + pendingRequests_.erase(pendingRequest); + return response; } void ClientConnection::getNextMessage() { - msg::BaseMessage baseMessage; - size_t baseMsgSize = baseMessage.getSize(); - vector buffer(baseMsgSize); - socketRead(&buffer[0], baseMsgSize); - baseMessage.deserialize(&buffer[0]); -// LOG(DEBUG) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << "\n"; - if (baseMessage.size > buffer.size()) - buffer.resize(baseMessage.size); -// { -// std::lock_guard socketLock(socketMutex_); - socketRead(&buffer[0], baseMessage.size); -// } - tv t; - baseMessage.received = t; + msg::BaseMessage baseMessage; + size_t baseMsgSize = baseMessage.getSize(); + vector buffer(baseMsgSize); + socketRead(&buffer[0], baseMsgSize); + baseMessage.deserialize(&buffer[0]); + // LOG(DEBUG) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << + //baseMessage.refersTo << "\n"; + if (baseMessage.size > buffer.size()) + buffer.resize(baseMessage.size); + // { + // std::lock_guard socketLock(socketMutex_); + socketRead(&buffer[0], baseMessage.size); + // } + tv t; + baseMessage.received = t; - { - std::unique_lock lock(pendingRequestsMutex_); -// LOG(DEBUG) << "got lock - getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << "\n"; - { - for (auto req: pendingRequests_) - { - if (req->id == baseMessage.refersTo) - { - req->response.reset(new msg::SerializedMessage()); - req->response->message = baseMessage; - req->response->buffer = (char*)malloc(baseMessage.size); - memcpy(req->response->buffer, &buffer[0], baseMessage.size); - lock.unlock(); - req->cv.notify_one(); - return; - } - } - } - } + { + std::unique_lock lock(pendingRequestsMutex_); + // LOG(DEBUG) << "got lock - getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", + //refers: " << baseMessage.refersTo << "\n"; + { + for (auto req : pendingRequests_) + { + if (req->id == baseMessage.refersTo) + { + req->response.reset(new msg::SerializedMessage()); + req->response->message = baseMessage; + req->response->buffer = (char*)malloc(baseMessage.size); + memcpy(req->response->buffer, &buffer[0], baseMessage.size); + lock.unlock(); + req->cv.notify_one(); + return; + } + } + } + } - if (messageReceiver_ != NULL) - messageReceiver_->onMessageReceived(this, baseMessage, &buffer[0]); + if (messageReceiver_ != NULL) + messageReceiver_->onMessageReceived(this, baseMessage, &buffer[0]); } void ClientConnection::reader() { - try - { - while(active_) - { - getNextMessage(); - } - } - catch (const std::exception& e) - { - if (messageReceiver_ != NULL) - messageReceiver_->onException(this, make_shared(e.what())); - } - catch (...) - { - } - connected_ = false; - active_ = false; + try + { + while (active_) + { + getNextMessage(); + } + } + catch (const std::exception& e) + { + if (messageReceiver_ != NULL) + messageReceiver_->onException(this, make_shared(e.what())); + } + catch (...) + { + } + connected_ = false; + active_ = false; } - - - - diff --git a/client/clientConnection.h b/client/clientConnection.h index a51f59c1..7e26403f 100644 --- a/client/clientConnection.h +++ b/client/clientConnection.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,16 +19,16 @@ #ifndef CLIENT_CONNECTION_H #define CLIENT_CONNECTION_H +#include "common/timeDefs.h" +#include "message/message.h" +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include "message/message.h" -#include "common/timeDefs.h" using asio::ip::tcp; @@ -40,11 +40,11 @@ class ClientConnection; /// Used to synchronize server requests (wait for server response) struct PendingRequest { - PendingRequest(uint16_t reqId) : id(reqId), response(NULL) {}; + PendingRequest(uint16_t reqId) : id(reqId), response(NULL){}; - uint16_t id; - std::shared_ptr response; - std::condition_variable cv; + uint16_t id; + std::shared_ptr response; + std::condition_variable cv; }; @@ -56,9 +56,9 @@ typedef std::shared_ptr shared_exception_ptr; class MessageReceiver { public: - virtual ~MessageReceiver() = default; - virtual void onMessageReceived(ClientConnection* connection, const msg::BaseMessage& baseMessage, char* buffer) = 0; - virtual void onException(ClientConnection* connection, shared_exception_ptr exception) = 0; + virtual ~MessageReceiver() = default; + virtual void onMessageReceived(ClientConnection* connection, const msg::BaseMessage& baseMessage, char* buffer) = 0; + virtual void onException(ClientConnection* connection, shared_exception_ptr exception) = 0; }; @@ -71,66 +71,62 @@ public: class ClientConnection { public: - /// ctor. Received message from the server are passed to MessageReceiver - ClientConnection(MessageReceiver* receiver, const std::string& host, size_t port); - virtual ~ClientConnection(); - virtual void start(); - virtual void stop(); - virtual bool send(const msg::BaseMessage* message) const; + /// ctor. Received message from the server are passed to MessageReceiver + ClientConnection(MessageReceiver* receiver, const std::string& host, size_t port); + virtual ~ClientConnection(); + virtual void start(); + virtual void stop(); + virtual bool send(const msg::BaseMessage* message) const; - /// Send request to the server and wait for answer - virtual std::shared_ptr sendRequest(const msg::BaseMessage* message, const chronos::msec& timeout = chronos::msec(1000)); + /// Send request to the server and wait for answer + virtual std::shared_ptr sendRequest(const msg::BaseMessage* message, const chronos::msec& timeout = chronos::msec(1000)); - /// Send request to the server and wait for answer of type T - template - std::shared_ptr sendReq(const msg::BaseMessage* message, const chronos::msec& timeout = chronos::msec(1000)) - { - std::shared_ptr reply = sendRequest(message, timeout); - if (!reply) - return NULL; - std::shared_ptr msg(new T); - msg->deserialize(reply->message, reply->buffer); - return msg; - } + /// Send request to the server and wait for answer of type T + template + std::shared_ptr sendReq(const msg::BaseMessage* message, const chronos::msec& timeout = chronos::msec(1000)) + { + std::shared_ptr reply = sendRequest(message, timeout); + if (!reply) + return NULL; + std::shared_ptr msg(new T); + msg->deserialize(reply->message, reply->buffer); + return msg; + } - std::string getMacAddress() const; + std::string getMacAddress() const; - virtual bool active() const - { - return active_; - } + virtual bool active() const + { + return active_; + } - virtual bool connected() const - { - return (socket_ != nullptr); -// return (connected_ && socket); - } + virtual bool connected() const + { + return (socket_ != nullptr); + // return (connected_ && socket); + } protected: - virtual void reader(); + virtual void reader(); - void socketRead(void* to, size_t bytes); - void getNextMessage(); + void socketRead(void* to, size_t bytes); + void getNextMessage(); - asio::io_service io_service_; - mutable std::mutex socketMutex_; - std::shared_ptr socket_; - std::atomic active_; - std::atomic connected_; - MessageReceiver* messageReceiver_; - mutable std::mutex pendingRequestsMutex_; - std::set> pendingRequests_; - uint16_t reqId_; - std::string host_; - size_t port_; - std::thread* readerThread_; - chronos::msec sumTimeout_; + asio::io_service io_service_; + mutable std::mutex socketMutex_; + std::shared_ptr socket_; + std::atomic active_; + std::atomic connected_; + MessageReceiver* messageReceiver_; + mutable std::mutex pendingRequestsMutex_; + std::set> pendingRequests_; + uint16_t reqId_; + std::string host_; + size_t port_; + std::thread* readerThread_; + chronos::msec sumTimeout_; }; #endif - - - - diff --git a/client/controller.cpp b/client/controller.cpp index a023e477..4ce7e041 100644 --- a/client/controller.cpp +++ b/client/controller.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,244 +16,235 @@ along with this program. If not, see . ***/ -#include -#include -#include #include "controller.h" #include "decoder/pcmDecoder.h" +#include +#include +#include #if defined(HAS_OGG) && (defined(HAS_TREMOR) || defined(HAS_VORBIS)) #include "decoder/oggDecoder.h" #endif #if defined(HAS_FLAC) #include "decoder/flacDecoder.h" #endif -#include "timeProvider.h" -#include "message/time.h" -#include "message/hello.h" -#include "common/snapException.h" #include "aixlog.hpp" +#include "common/snapException.h" +#include "message/hello.h" +#include "message/time.h" +#include "timeProvider.h" using namespace std; -Controller::Controller(const std::string& hostId, size_t instance, std::shared_ptr meta) : MessageReceiver(), - hostId_(hostId), - instance_(instance), - active_(false), - latency_(0), - stream_(nullptr), - decoder_(nullptr), - player_(nullptr), - meta_(meta), - serverSettings_(nullptr), - async_exception_(nullptr) +Controller::Controller(const std::string& hostId, size_t instance, std::shared_ptr meta) + : MessageReceiver(), hostId_(hostId), instance_(instance), active_(false), latency_(0), stream_(nullptr), decoder_(nullptr), player_(nullptr), meta_(meta), + serverSettings_(nullptr), async_exception_(nullptr) { } void Controller::onException(ClientConnection* connection, shared_exception_ptr exception) { - LOG(ERROR) << "Controller::onException: " << exception->what() << "\n"; - async_exception_ = exception; + LOG(ERROR) << "Controller::onException: " << exception->what() << "\n"; + async_exception_ = exception; } void Controller::onMessageReceived(ClientConnection* connection, const msg::BaseMessage& baseMessage, char* buffer) { - std::lock_guard lock(receiveMutex_); - if (baseMessage.type == message_type::kWireChunk) - { - if (stream_ && decoder_) - { - msg::PcmChunk* pcmChunk = new msg::PcmChunk(sampleFormat_, 0); - pcmChunk->deserialize(baseMessage, buffer); -// LOG(DEBUG) << "chunk: " << pcmChunk->payloadSize << ", sampleFormat: " << sampleFormat_.rate << "\n"; - if (decoder_->decode(pcmChunk)) - { -//TODO: do decoding in thread? - stream_->addChunk(pcmChunk); - //LOG(DEBUG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->getDuration() << ", sec: " << pcmChunk->timestamp.sec << ", usec: " << pcmChunk->timestamp.usec/1000 << ", type: " << pcmChunk->type << "\n"; - } - else - delete pcmChunk; - } - } - else if (baseMessage.type == message_type::kTime) - { - msg::Time reply; - reply.deserialize(baseMessage, buffer); - TimeProvider::getInstance().setDiff(reply.latency, reply.received - reply.sent);// ToServer(diff / 2); - } - else if (baseMessage.type == message_type::kServerSettings) - { - serverSettings_.reset(new msg::ServerSettings()); - serverSettings_->deserialize(baseMessage, buffer); - LOG(INFO) << "ServerSettings - buffer: " << serverSettings_->getBufferMs() << ", latency: " << serverSettings_->getLatency() << ", volume: " << serverSettings_->getVolume() << ", muted: " << serverSettings_->isMuted() << "\n"; - if (stream_ && player_) - { - player_->setVolume(serverSettings_->getVolume() / 100.); - player_->setMute(serverSettings_->isMuted()); - stream_->setBufferLen(serverSettings_->getBufferMs() - serverSettings_->getLatency()); - } - } - else if (baseMessage.type == message_type::kCodecHeader) - { - headerChunk_.reset(new msg::CodecHeader()); - headerChunk_->deserialize(baseMessage, buffer); + std::lock_guard lock(receiveMutex_); + if (baseMessage.type == message_type::kWireChunk) + { + if (stream_ && decoder_) + { + msg::PcmChunk* pcmChunk = new msg::PcmChunk(sampleFormat_, 0); + pcmChunk->deserialize(baseMessage, buffer); + // LOG(DEBUG) << "chunk: " << pcmChunk->payloadSize << ", sampleFormat: " << sampleFormat_.rate << "\n"; + if (decoder_->decode(pcmChunk)) + { + // TODO: do decoding in thread? + stream_->addChunk(pcmChunk); + // LOG(DEBUG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->getDuration() << ", sec: " << pcmChunk->timestamp.sec << + // ", usec: " << pcmChunk->timestamp.usec/1000 << ", type: " << pcmChunk->type << "\n"; + } + else + delete pcmChunk; + } + } + else if (baseMessage.type == message_type::kTime) + { + msg::Time reply; + reply.deserialize(baseMessage, buffer); + TimeProvider::getInstance().setDiff(reply.latency, reply.received - reply.sent); // ToServer(diff / 2); + } + else if (baseMessage.type == message_type::kServerSettings) + { + serverSettings_.reset(new msg::ServerSettings()); + serverSettings_->deserialize(baseMessage, buffer); + LOG(INFO) << "ServerSettings - buffer: " << serverSettings_->getBufferMs() << ", latency: " << serverSettings_->getLatency() + << ", volume: " << serverSettings_->getVolume() << ", muted: " << serverSettings_->isMuted() << "\n"; + if (stream_ && player_) + { + player_->setVolume(serverSettings_->getVolume() / 100.); + player_->setMute(serverSettings_->isMuted()); + stream_->setBufferLen(serverSettings_->getBufferMs() - serverSettings_->getLatency()); + } + } + else if (baseMessage.type == message_type::kCodecHeader) + { + headerChunk_.reset(new msg::CodecHeader()); + headerChunk_->deserialize(baseMessage, buffer); - LOG(INFO) << "Codec: " << headerChunk_->codec << "\n"; - decoder_.reset(nullptr); - stream_ = nullptr; - player_.reset(nullptr); + LOG(INFO) << "Codec: " << headerChunk_->codec << "\n"; + decoder_.reset(nullptr); + stream_ = nullptr; + player_.reset(nullptr); - if (headerChunk_->codec == "pcm") - decoder_.reset(new PcmDecoder()); + if (headerChunk_->codec == "pcm") + decoder_.reset(new PcmDecoder()); #if defined(HAS_OGG) && (defined(HAS_TREMOR) || defined(HAS_VORBIS)) - else if (headerChunk_->codec == "ogg") - decoder_.reset(new OggDecoder()); + else if (headerChunk_->codec == "ogg") + decoder_.reset(new OggDecoder()); #endif #if defined(HAS_FLAC) - else if (headerChunk_->codec == "flac") - decoder_.reset(new FlacDecoder()); + else if (headerChunk_->codec == "flac") + decoder_.reset(new FlacDecoder()); #endif - else - throw SnapException("codec not supported: \"" + headerChunk_->codec + "\""); + else + throw SnapException("codec not supported: \"" + headerChunk_->codec + "\""); - sampleFormat_ = decoder_->setHeader(headerChunk_.get()); - LOG(NOTICE) << TAG("state") << "sampleformat: " << sampleFormat_.rate << ":" << sampleFormat_.bits << ":" << sampleFormat_.channels << "\n"; + sampleFormat_ = decoder_->setHeader(headerChunk_.get()); + LOG(NOTICE) << TAG("state") << "sampleformat: " << sampleFormat_.rate << ":" << sampleFormat_.bits << ":" << sampleFormat_.channels << "\n"; - stream_ = make_shared(sampleFormat_); - stream_->setBufferLen(serverSettings_->getBufferMs() - latency_); + stream_ = make_shared(sampleFormat_); + stream_->setBufferLen(serverSettings_->getBufferMs() - latency_); #ifdef HAS_ALSA - player_.reset(new AlsaPlayer(pcmDevice_, stream_)); + player_.reset(new AlsaPlayer(pcmDevice_, stream_)); #elif HAS_OPENSL - player_.reset(new OpenslPlayer(pcmDevice_, stream_)); + player_.reset(new OpenslPlayer(pcmDevice_, stream_)); #elif HAS_COREAUDIO - player_.reset(new CoreAudioPlayer(pcmDevice_, stream_)); + player_.reset(new CoreAudioPlayer(pcmDevice_, stream_)); #else - throw SnapException("No audio player support"); + throw SnapException("No audio player support"); #endif - player_->setVolume(serverSettings_->getVolume() / 100.); - player_->setMute(serverSettings_->isMuted()); - player_->start(); - } - else if (baseMessage.type == message_type::kStreamTags) - { - streamTags_.reset(new msg::StreamTags()); - streamTags_->deserialize(baseMessage, buffer); - - if(meta_) - meta_->push(streamTags_->msg); - } + player_->setVolume(serverSettings_->getVolume() / 100.); + player_->setMute(serverSettings_->isMuted()); + player_->start(); + } + else if (baseMessage.type == message_type::kStreamTags) + { + streamTags_.reset(new msg::StreamTags()); + streamTags_->deserialize(baseMessage, buffer); - if (baseMessage.type != message_type::kTime) - if (sendTimeSyncMessage(1000)) - LOG(DEBUG) << "time sync onMessageReceived\n"; + if (meta_) + meta_->push(streamTags_->msg); + } + + if (baseMessage.type != message_type::kTime) + if (sendTimeSyncMessage(1000)) + LOG(DEBUG) << "time sync onMessageReceived\n"; } bool Controller::sendTimeSyncMessage(long after) { - static long lastTimeSync(0); - long now = chronos::getTickCount(); - if (lastTimeSync + after > now) - return false; + static long lastTimeSync(0); + long now = chronos::getTickCount(); + if (lastTimeSync + after > now) + return false; - lastTimeSync = now; - msg::Time timeReq; - clientConnection_->send(&timeReq); - return true; + lastTimeSync = now; + msg::Time timeReq; + clientConnection_->send(&timeReq); + return true; } void Controller::start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency) { - pcmDevice_ = pcmDevice; - latency_ = latency; - clientConnection_.reset(new ClientConnection(this, host, port)); - controllerThread_ = thread(&Controller::worker, this); + pcmDevice_ = pcmDevice; + latency_ = latency; + clientConnection_.reset(new ClientConnection(this, host, port)); + controllerThread_ = thread(&Controller::worker, this); } void Controller::stop() { - LOG(DEBUG) << "Stopping Controller" << endl; - active_ = false; - controllerThread_.join(); - clientConnection_->stop(); + LOG(DEBUG) << "Stopping Controller" << endl; + active_ = false; + controllerThread_.join(); + clientConnection_->stop(); } void Controller::worker() { - active_ = true; + active_ = true; - while (active_) - { - try - { - clientConnection_->start(); + while (active_) + { + try + { + clientConnection_->start(); - string macAddress = clientConnection_->getMacAddress(); - if (hostId_.empty()) - hostId_ = ::getHostId(macAddress); + string macAddress = clientConnection_->getMacAddress(); + if (hostId_.empty()) + hostId_ = ::getHostId(macAddress); - /// Say hello to the server - msg::Hello hello(macAddress, hostId_, instance_); - clientConnection_->send(&hello); + /// Say hello to the server + msg::Hello hello(macAddress, hostId_, instance_); + clientConnection_->send(&hello); - /// Do initial time sync with the server - msg::Time timeReq; - for (size_t n=0; n<50 && active_; ++n) - { - if (async_exception_) - { - LOG(DEBUG) << "Async exception: " << async_exception_->what() << "\n"; - throw SnapException(async_exception_->what()); - } + /// Do initial time sync with the server + msg::Time timeReq; + for (size_t n = 0; n < 50 && active_; ++n) + { + if (async_exception_) + { + LOG(DEBUG) << "Async exception: " << async_exception_->what() << "\n"; + throw SnapException(async_exception_->what()); + } - shared_ptr reply = clientConnection_->sendReq(&timeReq, chronos::msec(2000)); - if (reply) - { - TimeProvider::getInstance().setDiff(reply->latency, reply->received - reply->sent); - chronos::usleep(100); - } - } - LOG(INFO) << "diff to server [ms]: " << (float)TimeProvider::getInstance().getDiffToServer().count() / 1000.f << "\n"; + shared_ptr reply = clientConnection_->sendReq(&timeReq, chronos::msec(2000)); + if (reply) + { + TimeProvider::getInstance().setDiff(reply->latency, reply->received - reply->sent); + chronos::usleep(100); + } + } + LOG(INFO) << "diff to server [ms]: " << (float)TimeProvider::getInstance().getDiffToServer().count() / 1000.f << "\n"; - /// Main loop - while (active_) - { - LOG(DEBUG) << "Main loop\n"; - for (size_t n=0; n<10 && active_; ++n) - { - chronos::sleep(100); - if (async_exception_) - { - LOG(DEBUG) << "Async exception: " << async_exception_->what() << "\n"; - throw SnapException(async_exception_->what()); - } - } + /// Main loop + while (active_) + { + LOG(DEBUG) << "Main loop\n"; + for (size_t n = 0; n < 10 && active_; ++n) + { + chronos::sleep(100); + if (async_exception_) + { + LOG(DEBUG) << "Async exception: " << async_exception_->what() << "\n"; + throw SnapException(async_exception_->what()); + } + } - if (sendTimeSyncMessage(5000)) - LOG(DEBUG) << "time sync main loop\n"; - } - } - catch (const std::exception& e) - { - async_exception_ = nullptr; - SLOG(ERROR) << "Exception in Controller::worker(): " << e.what() << endl; - clientConnection_->stop(); - player_.reset(); - stream_.reset(); - decoder_.reset(); - for (size_t n=0; (n<10) && active_; ++n) - chronos::sleep(100); - } - } - LOG(DEBUG) << "Thread stopped\n"; + if (sendTimeSyncMessage(5000)) + LOG(DEBUG) << "time sync main loop\n"; + } + } + catch (const std::exception& e) + { + async_exception_ = nullptr; + SLOG(ERROR) << "Exception in Controller::worker(): " << e.what() << endl; + clientConnection_->stop(); + player_.reset(); + stream_.reset(); + decoder_.reset(); + for (size_t n = 0; (n < 10) && active_; ++n) + chronos::sleep(100); + } + } + LOG(DEBUG) << "Thread stopped\n"; } - - - diff --git a/client/controller.h b/client/controller.h index 2fb56f4e..eb2c7415 100644 --- a/client/controller.h +++ b/client/controller.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,13 +19,13 @@ #ifndef CONTROLLER_H #define CONTROLLER_H -#include -#include #include "decoder/decoder.h" #include "message/message.h" #include "message/serverSettings.h" #include "message/streamTags.h" #include "player/pcmDevice.h" +#include +#include #ifdef HAS_ALSA #include "player/alsaPlayer.h" #elif HAS_OPENSL @@ -34,56 +34,55 @@ #include "player/coreAudioPlayer.h" #endif #include "clientConnection.h" -#include "stream.h" #include "metadata.h" +#include "stream.h" /// Forwards PCM data to the audio player /** * Sets up a connection to the server (using ClientConnection) - * Sets up the audio decoder and player. + * Sets up the audio decoder and player. * Decodes audio (message_type::kWireChunk) and feeds PCM to the audio stream buffer * Does timesync with the server */ class Controller : public MessageReceiver { public: - Controller(const std::string& clientId, size_t instance, std::shared_ptr meta); - void start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency); - void stop(); + Controller(const std::string& clientId, size_t instance, std::shared_ptr meta); + void start(const PcmDevice& pcmDevice, const std::string& host, size_t port, int latency); + void stop(); - /// Implementation of MessageReceiver. - /// ClientConnection passes messages from the server through these callbacks - virtual void onMessageReceived(ClientConnection* connection, const msg::BaseMessage& baseMessage, char* buffer); + /// Implementation of MessageReceiver. + /// ClientConnection passes messages from the server through these callbacks + virtual void onMessageReceived(ClientConnection* connection, const msg::BaseMessage& baseMessage, char* buffer); - /// Implementation of MessageReceiver. - /// Used for async exception reporting - virtual void onException(ClientConnection* connection, shared_exception_ptr exception); + /// Implementation of MessageReceiver. + /// Used for async exception reporting + virtual void onException(ClientConnection* connection, shared_exception_ptr exception); private: - void worker(); - bool sendTimeSyncMessage(long after = 1000); - std::string hostId_; - std::string meta_callback_; - size_t instance_; - std::atomic active_; - std::thread controllerThread_; - SampleFormat sampleFormat_; - PcmDevice pcmDevice_; - int latency_; - std::unique_ptr clientConnection_; - std::shared_ptr stream_; - std::unique_ptr decoder_; - std::unique_ptr player_; - std::shared_ptr meta_; - std::shared_ptr serverSettings_; - std::shared_ptr streamTags_; - std::shared_ptr headerChunk_; - std::mutex receiveMutex_; + void worker(); + bool sendTimeSyncMessage(long after = 1000); + std::string hostId_; + std::string meta_callback_; + size_t instance_; + std::atomic active_; + std::thread controllerThread_; + SampleFormat sampleFormat_; + PcmDevice pcmDevice_; + int latency_; + std::unique_ptr clientConnection_; + std::shared_ptr stream_; + std::unique_ptr decoder_; + std::unique_ptr player_; + std::shared_ptr meta_; + std::shared_ptr serverSettings_; + std::shared_ptr streamTags_; + std::shared_ptr headerChunk_; + std::mutex receiveMutex_; - shared_exception_ptr async_exception_; + shared_exception_ptr async_exception_; }; #endif - diff --git a/client/decoder/decoder.h b/client/decoder/decoder.h index 0701a55c..ca3c131c 100644 --- a/client/decoder/decoder.h +++ b/client/decoder/decoder.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -18,25 +18,23 @@ #ifndef DECODER_H #define DECODER_H -#include -#include "message/pcmChunk.h" -#include "message/codecHeader.h" #include "common/sampleFormat.h" +#include "message/codecHeader.h" +#include "message/pcmChunk.h" +#include class Decoder { public: - Decoder() {}; - virtual ~Decoder() {}; - virtual bool decode(msg::PcmChunk* chunk) = 0; - virtual SampleFormat setHeader(msg::CodecHeader* chunk) = 0; + Decoder(){}; + virtual ~Decoder(){}; + virtual bool decode(msg::PcmChunk* chunk) = 0; + virtual SampleFormat setHeader(msg::CodecHeader* chunk) = 0; protected: - std::mutex mutex_; + std::mutex mutex_; }; #endif - - diff --git a/client/decoder/flacDecoder.cpp b/client/decoder/flacDecoder.cpp index 4ca1d1ea..8489f86f 100644 --- a/client/decoder/flacDecoder.cpp +++ b/client/decoder/flacDecoder.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,215 +16,209 @@ along with this program. If not, see . ***/ -#include -#include -#include #include "flacDecoder.h" -#include "common/snapException.h" -#include "common/endian.hpp" #include "aixlog.hpp" +#include "common/endian.hpp" +#include "common/snapException.h" +#include +#include +#include using namespace std; -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 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 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 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::PcmChunk* flacChunk = NULL; static msg::PcmChunk* pcmChunk = NULL; static SampleFormat sampleFormat; -static FLAC__StreamDecoder *decoder = NULL; +static FLAC__StreamDecoder* decoder = NULL; FlacDecoder::FlacDecoder() : Decoder(), lastError_(nullptr) { - flacChunk = new msg::PcmChunk(); + flacChunk = new msg::PcmChunk(); } FlacDecoder::~FlacDecoder() { - std::lock_guard lock(mutex_); - delete flacChunk; - delete decoder; + std::lock_guard lock(mutex_); + delete flacChunk; + delete decoder; } bool FlacDecoder::decode(msg::PcmChunk* chunk) { - std::lock_guard lock(mutex_); - cacheInfo_.reset(); - pcmChunk = chunk; - flacChunk->payload = (char*)realloc(flacChunk->payload, chunk->payloadSize); - memcpy(flacChunk->payload, chunk->payload, chunk->payloadSize); - flacChunk->payloadSize = chunk->payloadSize; + std::lock_guard lock(mutex_); + cacheInfo_.reset(); + pcmChunk = chunk; + flacChunk->payload = (char*)realloc(flacChunk->payload, chunk->payloadSize); + memcpy(flacChunk->payload, chunk->payload, chunk->payloadSize); + flacChunk->payloadSize = chunk->payloadSize; - pcmChunk->payload = (char*)realloc(pcmChunk->payload, 0); - pcmChunk->payloadSize = 0; - while (flacChunk->payloadSize > 0) - { - if (!FLAC__stream_decoder_process_single(decoder)) - { - return false; - } + pcmChunk->payload = (char*)realloc(pcmChunk->payload, 0); + pcmChunk->payloadSize = 0; + while (flacChunk->payloadSize > 0) + { + if (!FLAC__stream_decoder_process_single(decoder)) + { + return false; + } - if (lastError_) - { - LOG(ERROR) << "FLAC decode error: " << FLAC__StreamDecoderErrorStatusString[*lastError_] << "\n"; - lastError_= nullptr; - return false; - } - } + if (lastError_) + { + LOG(ERROR) << "FLAC decode error: " << FLAC__StreamDecoderErrorStatusString[*lastError_] << "\n"; + lastError_ = nullptr; + return false; + } + } - if ((cacheInfo_.cachedBlocks_ > 0) && (cacheInfo_.sampleRate_ != 0)) - { - double diffMs = cacheInfo_.cachedBlocks_ / ((double)cacheInfo_.sampleRate_ / 1000.); - int32_t s = (diffMs / 1000); - int32_t us = (diffMs * 1000); - us %= 1000000; - LOG(DEBUG) << "Cached: " << cacheInfo_.cachedBlocks_ << ", " << diffMs << "ms, " << s << "s, " << us << "us\n"; - chunk->timestamp = chunk->timestamp - tv(s, us); - } - return true; + if ((cacheInfo_.cachedBlocks_ > 0) && (cacheInfo_.sampleRate_ != 0)) + { + double diffMs = cacheInfo_.cachedBlocks_ / ((double)cacheInfo_.sampleRate_ / 1000.); + int32_t s = (diffMs / 1000); + int32_t us = (diffMs * 1000); + us %= 1000000; + LOG(DEBUG) << "Cached: " << cacheInfo_.cachedBlocks_ << ", " << diffMs << "ms, " << s << "s, " << us << "us\n"; + chunk->timestamp = chunk->timestamp - tv(s, us); + } + return true; } SampleFormat FlacDecoder::setHeader(msg::CodecHeader* chunk) { - flacHeader = chunk; - FLAC__StreamDecoderInitStatus init_status; + flacHeader = chunk; + FLAC__StreamDecoderInitStatus init_status; - if ((decoder = FLAC__stream_decoder_new()) == NULL) - throw SnapException("ERROR: allocating decoder"); + if ((decoder = FLAC__stream_decoder_new()) == NULL) + throw SnapException("ERROR: allocating decoder"); -// (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); - if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) - throw SnapException("ERROR: initializing decoder: " + string(FLAC__StreamDecoderInitStatusString[init_status])); + // (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); + if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) + throw SnapException("ERROR: initializing decoder: " + string(FLAC__StreamDecoderInitStatusString[init_status])); - sampleFormat.rate = 0; - FLAC__stream_decoder_process_until_end_of_metadata(decoder); - if (sampleFormat.rate == 0) - throw SnapException("Sample format not found"); + sampleFormat.rate = 0; + FLAC__stream_decoder_process_until_end_of_metadata(decoder); + if (sampleFormat.rate == 0) + throw SnapException("Sample format not found"); - return sampleFormat; + return sampleFormat; } -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) - { - *bytes = flacHeader->payloadSize; - memcpy(buffer, flacHeader->payload, *bytes); - flacHeader = NULL; - } - else if (flacChunk != NULL) - { -// cerr << "read_callback: " << *bytes << ", avail: " << flacChunk->payloadSize << "\n"; - static_cast(client_data)->cacheInfo_.isCachedChunk_ = false; - if (*bytes > flacChunk->payloadSize) - *bytes = flacChunk->payloadSize; + if (flacHeader != NULL) + { + *bytes = flacHeader->payloadSize; + memcpy(buffer, flacHeader->payload, *bytes); + flacHeader = NULL; + } + else if (flacChunk != NULL) + { + // cerr << "read_callback: " << *bytes << ", avail: " << flacChunk->payloadSize << "\n"; + static_cast(client_data)->cacheInfo_.isCachedChunk_ = false; + if (*bytes > flacChunk->payloadSize) + *bytes = flacChunk->payloadSize; -// if (*bytes == 0) -// return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + // if (*bytes == 0) + // return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; - memcpy(buffer, flacChunk->payload, *bytes); - memmove(flacChunk->payload, flacChunk->payload + *bytes, flacChunk->payloadSize - *bytes); - flacChunk->payloadSize = flacChunk->payloadSize - *bytes; - flacChunk->payload = (char*)realloc(flacChunk->payload, flacChunk->payloadSize); - } - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + memcpy(buffer, flacChunk->payload, *bytes); + memmove(flacChunk->payload, flacChunk->payload + *bytes, flacChunk->payloadSize - *bytes); + flacChunk->payloadSize = flacChunk->payloadSize - *bytes; + flacChunk->payload = (char*)realloc(flacChunk->payload, flacChunk->payloadSize); + } + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } -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; - if (pcmChunk != NULL) - { - size_t bytes = frame->header.blocksize * sampleFormat.frameSize; + if (pcmChunk != NULL) + { + size_t bytes = frame->header.blocksize * sampleFormat.frameSize; - FlacDecoder* flacDecoder = static_cast(client_data); - if (flacDecoder->cacheInfo_.isCachedChunk_) - flacDecoder->cacheInfo_.cachedBlocks_ += frame->header.blocksize; + FlacDecoder* flacDecoder = static_cast(client_data); + if (flacDecoder->cacheInfo_.isCachedChunk_) + flacDecoder->cacheInfo_.cachedBlocks_ += frame->header.blocksize; - pcmChunk->payload = (char*)realloc(pcmChunk->payload, pcmChunk->payloadSize + bytes); + pcmChunk->payload = (char*)realloc(pcmChunk->payload, pcmChunk->payloadSize + bytes); - for (size_t channel = 0; channel < sampleFormat.channels; ++channel) - { - if (buffer[channel] == NULL) - { - SLOG(ERROR) << "ERROR: buffer[" << channel << "] is NULL\n"; - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - - if (sampleFormat.sampleSize == 1) - { - int8_t* chunkBuffer = (int8_t*)(pcmChunk->payload + pcmChunk->payloadSize); - for (size_t i = 0; i < frame->header.blocksize; i++) - chunkBuffer[sampleFormat.channels*i + channel] = (int8_t)(buffer[channel][i]); - } - else if (sampleFormat.sampleSize == 2) - { - int16_t* chunkBuffer = (int16_t*)(pcmChunk->payload + pcmChunk->payloadSize); - for (size_t i = 0; i < frame->header.blocksize; i++) - chunkBuffer[sampleFormat.channels*i + channel] = SWAP_16((int16_t)(buffer[channel][i])); - } - else if (sampleFormat.sampleSize == 4) - { - int32_t* chunkBuffer = (int32_t*)(pcmChunk->payload + pcmChunk->payloadSize); - for (size_t i = 0; i < frame->header.blocksize; i++) - chunkBuffer[sampleFormat.channels*i + channel] = SWAP_32((int32_t)(buffer[channel][i])); - } - } - pcmChunk->payloadSize += bytes; - } + for (size_t channel = 0; channel < sampleFormat.channels; ++channel) + { + if (buffer[channel] == NULL) + { + SLOG(ERROR) << "ERROR: buffer[" << channel << "] is NULL\n"; + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + if (sampleFormat.sampleSize == 1) + { + int8_t* chunkBuffer = (int8_t*)(pcmChunk->payload + pcmChunk->payloadSize); + for (size_t i = 0; i < frame->header.blocksize; i++) + chunkBuffer[sampleFormat.channels * i + channel] = (int8_t)(buffer[channel][i]); + } + else if (sampleFormat.sampleSize == 2) + { + int16_t* chunkBuffer = (int16_t*)(pcmChunk->payload + pcmChunk->payloadSize); + for (size_t i = 0; i < frame->header.blocksize; i++) + chunkBuffer[sampleFormat.channels * i + channel] = SWAP_16((int16_t)(buffer[channel][i])); + } + else if (sampleFormat.sampleSize == 4) + { + int32_t* chunkBuffer = (int32_t*)(pcmChunk->payload + pcmChunk->payloadSize); + for (size_t i = 0; i < frame->header.blocksize; i++) + chunkBuffer[sampleFormat.channels * i + channel] = SWAP_32((int32_t)(buffer[channel][i])); + } + } + pcmChunk->payloadSize += bytes; + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } -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; - /* print some stats */ - if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) - { - static_cast(client_data)->cacheInfo_.sampleRate_ = metadata->data.stream_info.sample_rate; - sampleFormat.setFormat( - metadata->data.stream_info.sample_rate, - metadata->data.stream_info.bits_per_sample, - metadata->data.stream_info.channels); - } + (void)decoder; + /* print some stats */ + if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) + { + static_cast(client_data)->cacheInfo_.sampleRate_ = metadata->data.stream_info.sample_rate; + sampleFormat.setFormat(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; - SLOG(ERROR) << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << "\n"; - static_cast(client_data)->lastError_ = std::unique_ptr(new FLAC__StreamDecoderErrorStatus(status)); + (void)decoder, (void)client_data; + SLOG(ERROR) << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << "\n"; + static_cast(client_data)->lastError_ = std::unique_ptr(new FLAC__StreamDecoderErrorStatus(status)); - /// TODO, see issue #120: - // Thu Nov 10 07:26:44 2016 daemon.warn dnsmasq-dhcp[1194]: no address range available for DHCP request via wlan0 - // Thu Nov 10 07:54:39 2016 daemon.err snapclient[1158]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC - // Thu Nov 10 07:54:39 2016 daemon.err snapclient[1158]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC - // - // and: - // Oct 27 17:37:38 kitchen snapclient[869]: Connected to 192.168.222.10 - // 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 + /// TODO, see issue #120: + // Thu Nov 10 07:26:44 2016 daemon.warn dnsmasq-dhcp[1194]: no address range available for DHCP request via wlan0 + // Thu Nov 10 07:54:39 2016 daemon.err snapclient[1158]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC + // Thu Nov 10 07:54:39 2016 daemon.err snapclient[1158]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC + // + // and: + // Oct 27 17:37:38 kitchen snapclient[869]: Connected to 192.168.222.10 + // 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 } - - - - - diff --git a/client/decoder/flacDecoder.h b/client/decoder/flacDecoder.h index 29e1799b..8d85f369 100644 --- a/client/decoder/flacDecoder.h +++ b/client/decoder/flacDecoder.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -29,36 +29,34 @@ struct CacheInfo { - CacheInfo() : sampleRate_(0) - { - reset(); - } + CacheInfo() : sampleRate_(0) + { + reset(); + } - void reset() - { - isCachedChunk_ = true; - cachedBlocks_ = 0; - } + void reset() + { + isCachedChunk_ = true; + cachedBlocks_ = 0; + } - bool isCachedChunk_; - size_t cachedBlocks_; - size_t sampleRate_; + bool isCachedChunk_; + size_t cachedBlocks_; + size_t sampleRate_; }; class FlacDecoder : public Decoder { public: - FlacDecoder(); - virtual ~FlacDecoder(); - virtual bool decode(msg::PcmChunk* chunk); - virtual SampleFormat setHeader(msg::CodecHeader* chunk); + FlacDecoder(); + virtual ~FlacDecoder(); + virtual bool decode(msg::PcmChunk* chunk); + virtual SampleFormat setHeader(msg::CodecHeader* chunk); - CacheInfo cacheInfo_; - std::unique_ptr lastError_; + CacheInfo cacheInfo_; + std::unique_ptr lastError_; }; #endif - - diff --git a/client/decoder/oggDecoder.cpp b/client/decoder/oggDecoder.cpp index 9c0542c5..58501e4c 100644 --- a/client/decoder/oggDecoder.cpp +++ b/client/decoder/oggDecoder.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,14 +16,14 @@ along with this program. If not, see . ***/ -#include -#include #include +#include +#include -#include "oggDecoder.h" -#include "common/snapException.h" -#include "common/endian.hpp" #include "aixlog.hpp" +#include "common/endian.hpp" +#include "common/snapException.h" +#include "oggDecoder.h" using namespace std; @@ -31,212 +31,210 @@ using namespace std; OggDecoder::OggDecoder() : Decoder() { - ogg_sync_init(&oy); /* Now we can read pages */ + ogg_sync_init(&oy); /* Now we can read pages */ } OggDecoder::~OggDecoder() { - std::lock_guard lock(mutex_); - vorbis_block_clear(&vb); - vorbis_dsp_clear(&vd); - ogg_stream_clear(&os); - vorbis_comment_clear(&vc); - vorbis_info_clear(&vi); /* must be called last */ - ogg_sync_clear(&oy); + std::lock_guard lock(mutex_); + vorbis_block_clear(&vb); + vorbis_dsp_clear(&vd); + ogg_stream_clear(&os); + vorbis_comment_clear(&vc); + vorbis_info_clear(&vi); /* must be called last */ + ogg_sync_clear(&oy); } bool OggDecoder::decode(msg::PcmChunk* chunk) { - std::lock_guard lock(mutex_); - /* grab some data at the head of the stream. We want the first page - (which is guaranteed to be small and only contain the Vorbis - stream initial header) We need the first page to get the stream - serialno. */ - int size = chunk->payloadSize; - char *buffer = ogg_sync_buffer(&oy, size); - memcpy(buffer, chunk->payload, size); - ogg_sync_wrote(&oy, size); + std::lock_guard lock(mutex_); + /* grab some data at the head of the stream. We want the first page + (which is guaranteed to be small and only contain the Vorbis + stream initial header) We need the first page to get the stream + serialno. */ + int size = chunk->payloadSize; + char* buffer = ogg_sync_buffer(&oy, size); + memcpy(buffer, chunk->payload, size); + ogg_sync_wrote(&oy, size); - chunk->payloadSize = 0; - /* The rest is just a straight decode loop until end of stream */ - // while(!eos){ - while(true) - { - int result = ogg_sync_pageout(&oy, &og); - if (result == 0) - break; /* need more data */ - if (result < 0) - { - /* missing or corrupt data at this page position */ - LOG(ERROR) << "Corrupt or missing data in bitstream; continuing...\n"; - continue; - } + chunk->payloadSize = 0; + /* The rest is just a straight decode loop until end of stream */ + // while(!eos){ + while (true) + { + int result = ogg_sync_pageout(&oy, &og); + if (result == 0) + break; /* need more data */ + if (result < 0) + { + /* missing or corrupt data at this page position */ + LOG(ERROR) << "Corrupt or missing data in bitstream; continuing...\n"; + continue; + } - ogg_stream_pagein(&os,&og); /* can safely ignore errors at this point */ - while(1) - { - result = ogg_stream_packetout(&os, &op); + ogg_stream_pagein(&os, &og); /* can safely ignore errors at this point */ + while (1) + { + result = ogg_stream_packetout(&os, &op); - if (result == 0) - break; /* need more data */ - if (result < 0) - continue; /* missing or corrupt data at this page position */ - /* no reason to complain; already complained above */ - /* we have a packet. Decode it */ + if (result == 0) + break; /* need more data */ + if (result < 0) + continue; /* missing or corrupt data at this page position */ + /* no reason to complain; already complained above */ + /* we have a packet. Decode it */ #ifdef HAS_TREMOR - ogg_int32_t **pcm; + ogg_int32_t** pcm; #else - float **pcm; + float** pcm; #endif - int samples; + int samples; - if (vorbis_synthesis(&vb,&op) == 0) /* test for success! */ - vorbis_synthesis_blockin(&vd, &vb); - /* - **pcm is a multichannel float vector. In stereo, for - example, pcm[0] is left, and pcm[1] is right. samples is - the size of each channel. Convert the float values - (-1.<=range<=1.) to whatever PCM format and write it out */ - while ((samples = vorbis_synthesis_pcmout(&vd, &pcm)) > 0) - { - size_t bytes = sampleFormat_.sampleSize * vi.channels * samples; - chunk->payload = (char*)realloc(chunk->payload, chunk->payloadSize + bytes); - for (int channel = 0; channel < vi.channels; ++channel) - { - if (sampleFormat_.sampleSize == 1) - { - int8_t* chunkBuffer = (int8_t*)(chunk->payload + chunk->payloadSize); - for (int i = 0; i < samples; i++) - { - int8_t& val = chunkBuffer[sampleFormat_.channels*i + channel]; + if (vorbis_synthesis(&vb, &op) == 0) /* test for success! */ + vorbis_synthesis_blockin(&vd, &vb); + /* + **pcm is a multichannel float vector. In stereo, for + example, pcm[0] is left, and pcm[1] is right. samples is + the size of each channel. Convert the float values + (-1.<=range<=1.) to whatever PCM format and write it out */ + while ((samples = vorbis_synthesis_pcmout(&vd, &pcm)) > 0) + { + size_t bytes = sampleFormat_.sampleSize * vi.channels * samples; + chunk->payload = (char*)realloc(chunk->payload, chunk->payloadSize + bytes); + for (int channel = 0; channel < vi.channels; ++channel) + { + if (sampleFormat_.sampleSize == 1) + { + int8_t* chunkBuffer = (int8_t*)(chunk->payload + chunk->payloadSize); + for (int i = 0; i < samples; i++) + { + int8_t& val = chunkBuffer[sampleFormat_.channels * i + channel]; #ifdef HAS_TREMOR - val = clip(pcm[channel][i], -128, 127); + val = clip(pcm[channel][i], -128, 127); #else - val = clip(floor(pcm[channel][i]*127.f + .5f), -128, 127); + val = clip(floor(pcm[channel][i] * 127.f + .5f), -128, 127); #endif - } - } - else if (sampleFormat_.sampleSize == 2) - { - int16_t* chunkBuffer = (int16_t*)(chunk->payload + chunk->payloadSize); - for (int i = 0; i < samples; i++) - { - int16_t& val = chunkBuffer[sampleFormat_.channels*i + channel]; + } + } + else if (sampleFormat_.sampleSize == 2) + { + int16_t* chunkBuffer = (int16_t*)(chunk->payload + chunk->payloadSize); + for (int i = 0; i < samples; i++) + { + int16_t& val = chunkBuffer[sampleFormat_.channels * i + channel]; #ifdef HAS_TREMOR - val = SWAP_16(clip(pcm[channel][i] >> 9, -32768, 32767)); + val = SWAP_16(clip(pcm[channel][i] >> 9, -32768, 32767)); #else - val = SWAP_16(clip(floor(pcm[channel][i]*32767.f + .5f), -32768, 32767)); + val = SWAP_16(clip(floor(pcm[channel][i] * 32767.f + .5f), -32768, 32767)); #endif - } - } - else if (sampleFormat_.sampleSize == 4) - { - int32_t* chunkBuffer = (int32_t*)(chunk->payload + chunk->payloadSize); - for (int i = 0; i < samples; i++) - { - int32_t& val = chunkBuffer[sampleFormat_.channels*i + channel]; + } + } + else if (sampleFormat_.sampleSize == 4) + { + int32_t* chunkBuffer = (int32_t*)(chunk->payload + chunk->payloadSize); + for (int i = 0; i < samples; i++) + { + int32_t& val = chunkBuffer[sampleFormat_.channels * i + channel]; #ifdef HAS_TREMOR - val = SWAP_32(clip(pcm[channel][i] << 7, -2147483648, 2147483647)); + val = SWAP_32(clip(pcm[channel][i] << 7, -2147483648, 2147483647)); #else - val = SWAP_32(clip(floor(pcm[channel][i]*2147483647.f + .5f), -2147483648, 2147483647)); + val = SWAP_32(clip(floor(pcm[channel][i] * 2147483647.f + .5f), -2147483648, 2147483647)); #endif - } - } - } + } + } + } - chunk->payloadSize += bytes; - vorbis_synthesis_read(&vd, samples); - } - } - } + chunk->payloadSize += bytes; + vorbis_synthesis_read(&vd, samples); + } + } + } - return true; + return true; } SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk) { - int size = chunk->payloadSize; - char *buffer = ogg_sync_buffer(&oy, size); - memcpy(buffer, chunk->payload, size); - ogg_sync_wrote(&oy, size); + int size = chunk->payloadSize; + char* buffer = ogg_sync_buffer(&oy, size); + memcpy(buffer, chunk->payload, size); + ogg_sync_wrote(&oy, size); - if (ogg_sync_pageout(&oy, &og) != 1) - throw SnapException("Input does not appear to be an Ogg bitstream"); + if (ogg_sync_pageout(&oy, &og) != 1) + 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_comment_init(&vc); - if (ogg_stream_pagein(&os, &og) < 0) - throw SnapException("Error reading first page of Ogg bitstream data"); + vorbis_info_init(&vi); + vorbis_comment_init(&vc); + if (ogg_stream_pagein(&os, &og) < 0) + throw SnapException("Error reading first page of Ogg bitstream data"); - if (ogg_stream_packetout(&os, &op) != 1) - throw SnapException("Error reading initial header packet"); + if (ogg_stream_packetout(&os, &op) != 1) + throw SnapException("Error reading initial header packet"); - if (vorbis_synthesis_headerin(&vi, &vc, &op) < 0) - throw SnapException("This Ogg bitstream does not contain Vorbis audio data"); + if (vorbis_synthesis_headerin(&vi, &vc, &op) < 0) + throw SnapException("This Ogg bitstream does not contain Vorbis audio data"); - int i(0); - while (i < 2) - { - while (i < 2) - { - int result=ogg_sync_pageout(&oy, &og); - if (result == 0) - break; /* Need more data */ - /* Don't complain about missing or corrupt data yet. We'll - catch it at the packet output phase */ - if (result == 1) - { - ogg_stream_pagein(&os, &og); /* we can ignore any errors here as they'll also become apparent at packetout */ - while (i < 2) - { - result=ogg_stream_packetout(&os, &op); - if (result == 0) - break; - /// Uh oh; data at some point was corrupted or missing! - /// We can't tolerate that in a header. Die. */ - if (result < 0) - throw SnapException("Corrupt secondary header. Exiting."); + int i(0); + while (i < 2) + { + while (i < 2) + { + int result = ogg_sync_pageout(&oy, &og); + if (result == 0) + break; /* Need more data */ + /* Don't complain about missing or corrupt data yet. We'll + catch it at the packet output phase */ + if (result == 1) + { + ogg_stream_pagein(&os, &og); /* we can ignore any errors here as they'll also become apparent at packetout */ + while (i < 2) + { + result = ogg_stream_packetout(&os, &op); + if (result == 0) + break; + /// Uh oh; data at some point was corrupted or missing! + /// We can't tolerate that in a header. Die. */ + if (result < 0) + throw SnapException("Corrupt secondary header. Exiting."); - result=vorbis_synthesis_headerin(&vi, &vc, &op); - if (result < 0) - throw SnapException("Corrupt secondary header. Exiting."); + result = vorbis_synthesis_headerin(&vi, &vc, &op); + if (result < 0) + throw SnapException("Corrupt secondary header. Exiting."); - i++; - } - } - } - } + i++; + } + } + } + } - /// OK, got and parsed all three headers. Initialize the Vorbis packet->PCM decoder. - if (vorbis_synthesis_init(&vd, &vi) == 0) - vorbis_block_init(&vd, &vb); - /// central decode state - /// local state for most of the decode so multiple block decodes can proceed - /// in parallel. We could init multiple vorbis_block structures for vd here + /// OK, got and parsed all three headers. Initialize the Vorbis packet->PCM decoder. + if (vorbis_synthesis_init(&vd, &vi) == 0) + vorbis_block_init(&vd, &vb); + /// central decode state + /// local state for most of the decode so multiple block decodes can proceed + /// in parallel. We could init multiple vorbis_block structures for vd here - 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 */ - char **ptr=vc.user_comments; - while (*ptr) - { - std::string comment(*ptr); - if (comment.find("SAMPLE_FORMAT=") == 0) - sampleFormat_.setFormat(comment.substr(comment.find("=") + 1)); - LOG(INFO) << "comment: " << comment << "\n";; - ++ptr; - } + /* Throw the comments plus a few lines about the bitstream we're decoding */ + char** ptr = vc.user_comments; + while (*ptr) + { + std::string comment(*ptr); + if (comment.find("SAMPLE_FORMAT=") == 0) + sampleFormat_.setFormat(comment.substr(comment.find("=") + 1)); + LOG(INFO) << "comment: " << comment << "\n"; + ; + ++ptr; + } - LOG(INFO) << "Encoded by: " << vc.vendor << "\n"; + LOG(INFO) << "Encoded by: " << vc.vendor << "\n"; - return sampleFormat_; + return sampleFormat_; } - - - diff --git a/client/decoder/oggDecoder.h b/client/decoder/oggDecoder.h index 93f1f700..bc9dddcf 100644 --- a/client/decoder/oggDecoder.h +++ b/client/decoder/oggDecoder.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -29,35 +29,35 @@ class OggDecoder : public Decoder { public: - OggDecoder(); - virtual ~OggDecoder(); - virtual bool decode(msg::PcmChunk* chunk); - virtual SampleFormat setHeader(msg::CodecHeader* chunk); + OggDecoder(); + virtual ~OggDecoder(); + virtual bool decode(msg::PcmChunk* chunk); + virtual SampleFormat setHeader(msg::CodecHeader* chunk); private: - bool decodePayload(msg::PcmChunk* chunk); - template - T clip(const T& value, const T& lower, const T& upper) const - { - if (value > upper) return upper; - if (value < lower) return lower; - return value; - } + bool decodePayload(msg::PcmChunk* chunk); + template + T clip(const T& value, const T& lower, const T& upper) const + { + if (value > upper) + return upper; + if (value < lower) + return lower; + return value; + } - ogg_sync_state oy; /// sync and verify incoming physical bitstream - ogg_stream_state os; /// take physical pages, weld into a logical stream of packets - ogg_page og; /// one Ogg bitstream page. Vorbis packets are inside - ogg_packet op; /// one raw packet of data for decode + ogg_sync_state oy; /// sync and verify incoming physical bitstream + ogg_stream_state os; /// take physical pages, weld into a logical stream of packets + ogg_page og; /// one Ogg bitstream page. Vorbis packets are inside + ogg_packet op; /// one raw packet of data for decode - vorbis_info vi; /// struct that stores all the static vorbis bitstream settings - vorbis_comment vc; /// struct that stores all the bitstream user comments - vorbis_dsp_state vd; /// central working state for the packet->PCM decoder - vorbis_block vb; /// local working space for packet->PCM decode + vorbis_info vi; /// struct that stores all the static vorbis bitstream settings + vorbis_comment vc; /// struct that stores all the bitstream user comments + vorbis_dsp_state vd; /// central working state for the packet->PCM decoder + vorbis_block vb; /// local working space for packet->PCM decode - SampleFormat sampleFormat_; + SampleFormat sampleFormat_; }; #endif - - diff --git a/client/decoder/pcmDecoder.cpp b/client/decoder/pcmDecoder.cpp index 261cae7d..c61c0cfe 100644 --- a/client/decoder/pcmDecoder.cpp +++ b/client/decoder/pcmDecoder.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,40 +16,40 @@ along with this program. If not, see . ***/ -#include "common/snapException.h" -#include "common/endian.hpp" -#include "aixlog.hpp" #include "pcmDecoder.h" +#include "aixlog.hpp" +#include "common/endian.hpp" +#include "common/snapException.h" #define ID_RIFF 0x46464952 #define ID_WAVE 0x45564157 -#define ID_FMT 0x20746d66 +#define ID_FMT 0x20746d66 #define ID_DATA 0x61746164 struct riff_wave_header { - uint32_t riff_id; - uint32_t riff_sz; - uint32_t wave_id; + uint32_t riff_id; + uint32_t riff_sz; + uint32_t wave_id; }; struct chunk_header { - uint32_t id; - uint32_t sz; + uint32_t id; + uint32_t sz; }; struct chunk_fmt { - uint16_t audio_format; - uint16_t num_channels; - uint32_t sample_rate; - uint32_t byte_rate; - uint16_t block_align; - uint16_t bits_per_sample; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; }; @@ -60,69 +60,61 @@ PcmDecoder::PcmDecoder() : Decoder() bool PcmDecoder::decode(msg::PcmChunk* chunk) { - return true; + return true; } SampleFormat PcmDecoder::setHeader(msg::CodecHeader* chunk) { - if (chunk->payloadSize < 44) - throw SnapException("PCM header too small"); + if (chunk->payloadSize < 44) + throw SnapException("PCM header too small"); struct riff_wave_header riff_wave_header; - struct chunk_header chunk_header; - struct chunk_fmt chunk_fmt; - chunk_fmt.sample_rate = SWAP_32(0); - chunk_fmt.bits_per_sample = SWAP_16(0); - chunk_fmt.num_channels = SWAP_16(0); + struct chunk_header chunk_header; + struct chunk_fmt chunk_fmt; + chunk_fmt.sample_rate = SWAP_32(0); + chunk_fmt.bits_per_sample = SWAP_16(0); + chunk_fmt.num_channels = SWAP_16(0); - size_t pos(0); - memcpy(&riff_wave_header, chunk->payload + pos, sizeof(riff_wave_header)); - pos += sizeof(riff_wave_header); - if ((SWAP_32(riff_wave_header.riff_id) != ID_RIFF) || (SWAP_32(riff_wave_header.wave_id) != ID_WAVE)) - throw SnapException("Not a riff/wave header"); + size_t pos(0); + memcpy(&riff_wave_header, chunk->payload + pos, sizeof(riff_wave_header)); + pos += sizeof(riff_wave_header); + if ((SWAP_32(riff_wave_header.riff_id) != ID_RIFF) || (SWAP_32(riff_wave_header.wave_id) != ID_WAVE)) + throw SnapException("Not a riff/wave header"); - bool moreChunks(true); - do - { - if (pos + sizeof(chunk_header) > chunk->payloadSize) - throw SnapException("riff/wave header incomplete"); - memcpy(&chunk_header, chunk->payload + pos, sizeof(chunk_header)); - pos += sizeof(chunk_header); - switch (SWAP_32(chunk_header.id)) - { - case ID_FMT: - if (pos + sizeof(chunk_fmt) > chunk->payloadSize) - throw SnapException("riff/wave header incomplete"); - memcpy(&chunk_fmt, chunk->payload + pos, sizeof(chunk_fmt)); - pos += sizeof(chunk_fmt); - /// If the format header is larger, skip the rest - if (SWAP_32(chunk_header.sz) > sizeof(chunk_fmt)) - pos += (SWAP_32(chunk_header.sz) - sizeof(chunk_fmt)); - break; - case ID_DATA: - /// Stop looking for chunks - moreChunks = false; - break; - default: - /// Unknown chunk, skip bytes - pos += SWAP_32(chunk_header.sz); - } - } - while (moreChunks); + bool moreChunks(true); + do + { + if (pos + sizeof(chunk_header) > chunk->payloadSize) + throw SnapException("riff/wave header incomplete"); + memcpy(&chunk_header, chunk->payload + pos, sizeof(chunk_header)); + pos += sizeof(chunk_header); + switch (SWAP_32(chunk_header.id)) + { + case ID_FMT: + if (pos + sizeof(chunk_fmt) > chunk->payloadSize) + throw SnapException("riff/wave header incomplete"); + memcpy(&chunk_fmt, chunk->payload + pos, sizeof(chunk_fmt)); + pos += sizeof(chunk_fmt); + /// If the format header is larger, skip the rest + if (SWAP_32(chunk_header.sz) > sizeof(chunk_fmt)) + pos += (SWAP_32(chunk_header.sz) - sizeof(chunk_fmt)); + break; + case ID_DATA: + /// Stop looking for chunks + moreChunks = false; + break; + default: + /// Unknown chunk, skip bytes + pos += SWAP_32(chunk_header.sz); + } + } while (moreChunks); - if (SWAP_32(chunk_fmt.sample_rate) == 0) - throw SnapException("Sample format not found"); + if (SWAP_32(chunk_fmt.sample_rate) == 0) + throw SnapException("Sample format not found"); - SampleFormat sampleFormat( - SWAP_32(chunk_fmt.sample_rate), - SWAP_16(chunk_fmt.bits_per_sample), - SWAP_16(chunk_fmt.num_channels)); + SampleFormat sampleFormat(SWAP_32(chunk_fmt.sample_rate), SWAP_16(chunk_fmt.bits_per_sample), SWAP_16(chunk_fmt.num_channels)); - return sampleFormat; + return sampleFormat; } - - - - diff --git a/client/decoder/pcmDecoder.h b/client/decoder/pcmDecoder.h index 08b4946a..b0b49cdd 100644 --- a/client/decoder/pcmDecoder.h +++ b/client/decoder/pcmDecoder.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -24,12 +24,10 @@ class PcmDecoder : public Decoder { public: - PcmDecoder(); - virtual bool decode(msg::PcmChunk* chunk); - virtual SampleFormat setHeader(msg::CodecHeader* chunk); + PcmDecoder(); + virtual bool decode(msg::PcmChunk* chunk); + virtual SampleFormat setHeader(msg::CodecHeader* chunk); }; #endif - - diff --git a/client/doubleBuffer.h b/client/doubleBuffer.h index 4baea9a1..737066da 100644 --- a/client/doubleBuffer.h +++ b/client/doubleBuffer.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ #ifndef DOUBLE_BUFFER_H #define DOUBLE_BUFFER_H -#include -#include +#include +#include /// Size limited queue @@ -32,105 +32,102 @@ template class DoubleBuffer { public: - DoubleBuffer(size_t size = 10) : bufferSize(size) - { - } + DoubleBuffer(size_t size = 10) : bufferSize(size) + { + } - inline void add(const T& element) - { - buffer.push_back(element); - if (buffer.size() > bufferSize) - buffer.pop_front(); - } + inline void add(const T& element) + { + buffer.push_back(element); + if (buffer.size() > bufferSize) + buffer.pop_front(); + } - inline void add(T&& element) - { - buffer.push_back(std::move(element)); - if (buffer.size() > bufferSize) - buffer.pop_front(); - } + inline void add(T&& element) + { + buffer.push_back(std::move(element)); + if (buffer.size() > bufferSize) + buffer.pop_front(); + } - /// Median as mean over N values around the median - T median(unsigned int mean = 1) const - { - if (buffer.empty()) - return 0; - std::deque tmpBuffer(buffer.begin(), buffer.end()); - std::sort(tmpBuffer.begin(), tmpBuffer.end()); - if ((mean <= 1) || (tmpBuffer.size() < mean)) - return tmpBuffer[tmpBuffer.size() / 2]; - else - { - unsigned int low = tmpBuffer.size() / 2; - unsigned int high = low; - low -= mean/2; - high += mean/2; - T result((T)0); - for (unsigned int i=low; i<=high; ++i) - { - result += tmpBuffer[i]; - } - return result / mean; - } - } + /// Median as mean over N values around the median + T median(unsigned int mean = 1) const + { + if (buffer.empty()) + return 0; + std::deque tmpBuffer(buffer.begin(), buffer.end()); + std::sort(tmpBuffer.begin(), tmpBuffer.end()); + if ((mean <= 1) || (tmpBuffer.size() < mean)) + return tmpBuffer[tmpBuffer.size() / 2]; + else + { + unsigned int low = tmpBuffer.size() / 2; + unsigned int high = low; + low -= mean / 2; + high += mean / 2; + T result((T)0); + for (unsigned int i = low; i <= high; ++i) + { + result += tmpBuffer[i]; + } + return result / mean; + } + } - double mean() const - { - if (buffer.empty()) - return 0; - double mean = 0.; - for (size_t n=0; n tmpBuffer(buffer.begin(), buffer.end()); - std::sort(tmpBuffer.begin(), tmpBuffer.end()); - return tmpBuffer[(size_t)(tmpBuffer.size() * ((float)percentile / (float)100))]; - } + T percentile(unsigned int percentile) const + { + if (buffer.empty()) + return 0; + std::deque tmpBuffer(buffer.begin(), buffer.end()); + std::sort(tmpBuffer.begin(), tmpBuffer.end()); + return tmpBuffer[(size_t)(tmpBuffer.size() * ((float)percentile / (float)100))]; + } - inline bool full() const - { - return (buffer.size() == bufferSize); - } + inline bool full() const + { + return (buffer.size() == bufferSize); + } - inline void clear() - { - buffer.clear(); - } + inline void clear() + { + buffer.clear(); + } - inline size_t size() const - { - return buffer.size(); - } + inline size_t size() const + { + return buffer.size(); + } - inline bool empty() const - { - return (buffer.size() == 0); - } + inline bool empty() const + { + return (buffer.size() == 0); + } - void setSize(size_t size) - { - bufferSize = size; - } + void setSize(size_t size) + { + bufferSize = size; + } - const std::deque& getBuffer() const - { - return &buffer; - } + const std::deque& getBuffer() const + { + return &buffer; + } private: - size_t bufferSize; - std::deque buffer; - + size_t bufferSize; + std::deque buffer; }; #endif - - diff --git a/client/metadata.h b/client/metadata.h index 863e8f63..14131967 100644 --- a/client/metadata.h +++ b/client/metadata.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -35,70 +35,69 @@ using json = nlohmann::json; class MetadataAdapter { public: - MetadataAdapter() - { - reset(); - } + MetadataAdapter() + { + reset(); + } - virtual ~MetadataAdapter() = default; + virtual ~MetadataAdapter() = default; - void reset() - { - msg_.reset(new json); - } + void reset() + { + msg_.reset(new json); + } - std::string serialize() - { - return METADATA + ":" + msg_->dump(); - } + std::string serialize() + { + return METADATA + ":" + msg_->dump(); + } - void tag(std::string name, std::string value) - { - (*msg_)[name] = value; - } + void tag(std::string name, std::string value) + { + (*msg_)[name] = value; + } - std::string operator[](std::string key) - { - try - { - return (*msg_)[key]; - } - catch (std::domain_error&) - { - return std::string(); - } - } + std::string operator[](std::string key) + { + try + { + return (*msg_)[key]; + } + catch (std::domain_error&) + { + return std::string(); + } + } - virtual int push() - { - std::cout << serialize() << "\n"; - return 0; - } + virtual int push() + { + std::cout << serialize() << "\n"; + return 0; + } - int push(json& jtag) - { - msg_.reset(new json(jtag)); - return push(); - } + int push(json& jtag) + { + msg_.reset(new json(jtag)); + return push(); + } protected: - std::shared_ptr msg_; + std::shared_ptr msg_; }; /* * Send metadata to stderr as json */ -class MetaStderrAdapter: public MetadataAdapter +class MetaStderrAdapter : public MetadataAdapter { public: - using MetadataAdapter::push; - - int push() - { - std::cerr << serialize() << "\n"; - return 0; - } + using MetadataAdapter::push; + int push() + { + std::cerr << serialize() << "\n"; + return 0; + } }; #endif diff --git a/client/player/alsaPlayer.cpp b/client/player/alsaPlayer.cpp index 595a45e2..8dd29b8a 100644 --- a/client/player/alsaPlayer.cpp +++ b/client/player/alsaPlayer.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -26,271 +26,272 @@ using namespace std; -AlsaPlayer::AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream) : - Player(pcmDevice, stream), handle_(NULL), buff_(NULL) +AlsaPlayer::AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream) : Player(pcmDevice, stream), handle_(NULL), buff_(NULL) { } void AlsaPlayer::initAlsa() { - unsigned int tmp, rate; - int pcm, channels; - snd_pcm_hw_params_t *params; - int buff_size; + unsigned int tmp, rate; + int pcm, channels; + snd_pcm_hw_params_t* params; + int buff_size; - const SampleFormat& format = stream_->getFormat(); - rate = format.rate; - channels = format.channels; + const SampleFormat& format = stream_->getFormat(); + rate = format.rate; + channels = format.channels; - /* Open the PCM device in playback mode */ - if ((pcm = snd_pcm_open(&handle_, pcmDevice_.name.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0) - throw SnapException("Can't open " + pcmDevice_.name + " PCM device: " + snd_strerror(pcm)); + /* Open the PCM device in playback mode */ + if ((pcm = snd_pcm_open(&handle_, pcmDevice_.name.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0) + throw SnapException("Can't open " + pcmDevice_.name + " PCM device: " + snd_strerror(pcm)); - /* struct snd_pcm_playback_info_t pinfo; - if ( (pcm = snd_pcm_playback_info( pcm_handle, &pinfo )) < 0 ) - fprintf( stderr, "Error: playback info error: %s\n", snd_strerror( err ) ); - printf("buffer: '%d'\n", pinfo.buffer_size); - */ - /* Allocate parameters object and fill it with default values*/ - snd_pcm_hw_params_alloca(¶ms); + /* struct snd_pcm_playback_info_t pinfo; + if ( (pcm = snd_pcm_playback_info( pcm_handle, &pinfo )) < 0 ) + fprintf( stderr, "Error: playback info error: %s\n", snd_strerror( err ) ); + printf("buffer: '%d'\n", pinfo.buffer_size); + */ + /* Allocate parameters object and fill it with default values*/ + snd_pcm_hw_params_alloca(¶ms); - if ((pcm = snd_pcm_hw_params_any(handle_, params)) < 0) - throw SnapException("Can't fill params: " + string(snd_strerror(pcm))); + if ((pcm = snd_pcm_hw_params_any(handle_, params)) < 0) + throw SnapException("Can't fill params: " + string(snd_strerror(pcm))); - /* Set parameters */ - if ((pcm = snd_pcm_hw_params_set_access(handle_, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) - throw SnapException("Can't set interleaved mode: " + string(snd_strerror(pcm))); + /* Set parameters */ + if ((pcm = snd_pcm_hw_params_set_access(handle_, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + throw SnapException("Can't set interleaved mode: " + string(snd_strerror(pcm))); - snd_pcm_format_t snd_pcm_format; - if (format.bits == 8) - snd_pcm_format = SND_PCM_FORMAT_S8; - else if (format.bits == 16) - snd_pcm_format = SND_PCM_FORMAT_S16_LE; - else if ((format.bits == 24) && (format.sampleSize == 4)) - snd_pcm_format = SND_PCM_FORMAT_S24_LE; - else if (format.bits == 32) - snd_pcm_format = SND_PCM_FORMAT_S32_LE; - else - throw SnapException("Unsupported sample format: " + cpt::to_string(format.bits)); + snd_pcm_format_t snd_pcm_format; + if (format.bits == 8) + snd_pcm_format = SND_PCM_FORMAT_S8; + else if (format.bits == 16) + snd_pcm_format = SND_PCM_FORMAT_S16_LE; + else if ((format.bits == 24) && (format.sampleSize == 4)) + snd_pcm_format = SND_PCM_FORMAT_S24_LE; + else if (format.bits == 32) + snd_pcm_format = SND_PCM_FORMAT_S32_LE; + else + throw SnapException("Unsupported sample format: " + cpt::to_string(format.bits)); - pcm = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format); - if (pcm == -EINVAL) - { - if (snd_pcm_format == SND_PCM_FORMAT_S24_LE) - { - snd_pcm_format = SND_PCM_FORMAT_S32_LE; - volCorrection_ = 256; - } - if (snd_pcm_format == SND_PCM_FORMAT_S8) - { - snd_pcm_format = SND_PCM_FORMAT_U8; - } - } - - pcm = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format); - if (pcm < 0) - { - cerr << "error: " << pcm << "\n"; - stringstream ss; - ss << "Can't set format: " << string(snd_strerror(pcm)) << ", supported: "; - for (int format = 0; format <= (int)SND_PCM_FORMAT_LAST; format++) - { - snd_pcm_format_t snd_pcm_format = static_cast(format); - if (snd_pcm_hw_params_test_format(handle_, params, snd_pcm_format) == 0) - ss << snd_pcm_format_name(snd_pcm_format) << " "; - } - throw SnapException(ss.str()); - } + pcm = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format); + if (pcm == -EINVAL) + { + if (snd_pcm_format == SND_PCM_FORMAT_S24_LE) + { + snd_pcm_format = SND_PCM_FORMAT_S32_LE; + volCorrection_ = 256; + } + if (snd_pcm_format == SND_PCM_FORMAT_S8) + { + snd_pcm_format = SND_PCM_FORMAT_U8; + } + } - if ((pcm = snd_pcm_hw_params_set_channels(handle_, params, channels)) < 0) - throw SnapException("Can't set channels number: " + string(snd_strerror(pcm))); + pcm = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format); + if (pcm < 0) + { + cerr << "error: " << pcm << "\n"; + stringstream ss; + ss << "Can't set format: " << string(snd_strerror(pcm)) << ", supported: "; + for (int format = 0; format <= (int)SND_PCM_FORMAT_LAST; format++) + { + snd_pcm_format_t snd_pcm_format = static_cast(format); + if (snd_pcm_hw_params_test_format(handle_, params, snd_pcm_format) == 0) + ss << snd_pcm_format_name(snd_pcm_format) << " "; + } + throw SnapException(ss.str()); + } - if ((pcm = snd_pcm_hw_params_set_rate_near(handle_, params, &rate, 0)) < 0) - throw SnapException("Can't set rate: " + string(snd_strerror(pcm))); + if ((pcm = snd_pcm_hw_params_set_channels(handle_, params, channels)) < 0) + throw SnapException("Can't set channels number: " + string(snd_strerror(pcm))); - unsigned int period_time; - snd_pcm_hw_params_get_period_time_max(params, &period_time, 0); - if (period_time > PERIOD_TIME) - period_time = PERIOD_TIME; + if ((pcm = snd_pcm_hw_params_set_rate_near(handle_, params, &rate, 0)) < 0) + throw SnapException("Can't set rate: " + string(snd_strerror(pcm))); - unsigned int buffer_time = 4 * period_time; + unsigned int period_time; + snd_pcm_hw_params_get_period_time_max(params, &period_time, 0); + if (period_time > PERIOD_TIME) + period_time = PERIOD_TIME; - 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); + unsigned int buffer_time = 4 * period_time; -// 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) -// LOG(ERROR) << "Unable to set buffer size " << (long int)periodsize << ": " << snd_strerror(pcm) << "\n"; + 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); - /* Write parameters */ - if ((pcm = snd_pcm_hw_params(handle_, params)) < 0) - throw SnapException("Can't set harware parameters: " + string(snd_strerror(pcm))); + // 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) + // LOG(ERROR) << "Unable to set buffer size " << (long int)periodsize << ": " << snd_strerror(pcm) << "\n"; - /* Resume information */ - LOG(DEBUG) << "PCM name: " << snd_pcm_name(handle_) << "\n"; - LOG(DEBUG) << "PCM state: " << snd_pcm_state_name(snd_pcm_state(handle_)) << "\n"; - snd_pcm_hw_params_get_channels(params, &tmp); - LOG(DEBUG) << "channels: " << tmp << "\n"; + /* Write parameters */ + if ((pcm = snd_pcm_hw_params(handle_, params)) < 0) + throw SnapException("Can't set harware parameters: " + string(snd_strerror(pcm))); - snd_pcm_hw_params_get_rate(params, &tmp, 0); - LOG(DEBUG) << "rate: " << tmp << " bps\n"; + /* Resume information */ + LOG(DEBUG) << "PCM name: " << snd_pcm_name(handle_) << "\n"; + LOG(DEBUG) << "PCM state: " << snd_pcm_state_name(snd_pcm_state(handle_)) << "\n"; + snd_pcm_hw_params_get_channels(params, &tmp); + LOG(DEBUG) << "channels: " << tmp << "\n"; - /* Allocate buffer to hold single period */ - snd_pcm_hw_params_get_period_size(params, &frames_, 0); - LOG(INFO) << "frames: " << frames_ << "\n"; + snd_pcm_hw_params_get_rate(params, &tmp, 0); + LOG(DEBUG) << "rate: " << tmp << " bps\n"; - buff_size = frames_ * format.frameSize; //channels * 2 /* 2 -> sample size */; - buff_ = (char *) malloc(buff_size); + /* Allocate buffer to hold single period */ + snd_pcm_hw_params_get_period_size(params, &frames_, 0); + LOG(INFO) << "frames: " << frames_ << "\n"; - snd_pcm_hw_params_get_period_time(params, &tmp, NULL); - LOG(DEBUG) << "period time: " << tmp << "\n"; + buff_size = frames_ * format.frameSize; // channels * 2 /* 2 -> sample size */; + buff_ = (char*)malloc(buff_size); - snd_pcm_sw_params_t *swparams; - snd_pcm_sw_params_alloca(&swparams); - snd_pcm_sw_params_current(handle_, swparams); + snd_pcm_hw_params_get_period_time(params, &tmp, NULL); + LOG(DEBUG) << "period time: " << tmp << "\n"; - 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_stop_threshold(pcm_handle, swparams, frames_); - snd_pcm_sw_params(handle_, swparams); + snd_pcm_sw_params_t* swparams; + snd_pcm_sw_params_alloca(&swparams); + snd_pcm_sw_params_current(handle_, swparams); + + 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_stop_threshold(pcm_handle, swparams, frames_); + snd_pcm_sw_params(handle_, swparams); } void AlsaPlayer::uninitAlsa() { - if (handle_ != NULL) - { - snd_pcm_drain(handle_); - snd_pcm_close(handle_); - handle_ = NULL; - } + if (handle_ != NULL) + { + snd_pcm_drain(handle_); + snd_pcm_close(handle_); + handle_ = NULL; + } - if (buff_ != NULL) - { - free(buff_); - buff_ = NULL; - } + if (buff_ != NULL) + { + free(buff_); + buff_ = NULL; + } } void AlsaPlayer::start() { - initAlsa(); - Player::start(); + initAlsa(); + Player::start(); } AlsaPlayer::~AlsaPlayer() { - stop(); + stop(); } void AlsaPlayer::stop() { - Player::stop(); - uninitAlsa(); + Player::stop(); + uninitAlsa(); } void AlsaPlayer::worker() { - snd_pcm_sframes_t pcm; - snd_pcm_sframes_t framesDelay; - long lastChunkTick = chronos::getTickCount(); + snd_pcm_sframes_t pcm; + snd_pcm_sframes_t framesDelay; + long lastChunkTick = chronos::getTickCount(); - while (active_) - { - if (handle_ == NULL) - { - try - { - initAlsa(); - } - catch (const std::exception& e) - { - LOG(ERROR) << "Exception in initAlsa: " << e.what() << endl; - chronos::sleep(100); - } - } + while (active_) + { + if (handle_ == NULL) + { + try + { + initAlsa(); + } + catch (const std::exception& e) + { + LOG(ERROR) << "Exception in initAlsa: " << e.what() << endl; + chronos::sleep(100); + } + } -// snd_pcm_avail_delay(handle_, &framesAvail, &framesDelay); - snd_pcm_delay(handle_, &framesDelay); - chronos::usec delay((chronos::usec::rep) (1000 * (double) framesDelay / stream_->getFormat().msRate())); -// LOG(INFO) << "delay: " << framesDelay << ", delay[ms]: " << delay.count() / 1000 << "\n"; + // snd_pcm_avail_delay(handle_, &framesAvail, &framesDelay); + snd_pcm_delay(handle_, &framesDelay); + chronos::usec delay((chronos::usec::rep)(1000 * (double)framesDelay / stream_->getFormat().msRate())); + // LOG(INFO) << "delay: " << framesDelay << ", delay[ms]: " << delay.count() / 1000 << "\n"; - if (stream_->getPlayerChunk(buff_, delay, frames_)) - { - lastChunkTick = chronos::getTickCount(); - adjustVolume(buff_, frames_); - if ((pcm = snd_pcm_writei(handle_, buff_, frames_)) == -EPIPE) - { - LOG(ERROR) << "XRUN\n"; - snd_pcm_prepare(handle_); - } - else if (pcm < 0) - { - LOG(ERROR) << "ERROR. Can't write to PCM device: " << snd_strerror(pcm) << "\n"; - uninitAlsa(); - } - } - else - { - LOG(INFO) << "Failed to get chunk\n"; - while (active_ && !stream_->waitForChunk(100)) - { - LOG(DEBUG) << "Waiting for chunk\n"; - if ((handle_ != NULL) && (chronos::getTickCount() - lastChunkTick > 5000)) - { - LOG(NOTICE) << "No chunk received for 5000ms. Closing ALSA.\n"; - uninitAlsa(); - stream_->clearChunks(); - } - } - } - } + if (stream_->getPlayerChunk(buff_, delay, frames_)) + { + lastChunkTick = chronos::getTickCount(); + adjustVolume(buff_, frames_); + if ((pcm = snd_pcm_writei(handle_, buff_, frames_)) == -EPIPE) + { + LOG(ERROR) << "XRUN\n"; + snd_pcm_prepare(handle_); + } + else if (pcm < 0) + { + LOG(ERROR) << "ERROR. Can't write to PCM device: " << snd_strerror(pcm) << "\n"; + uninitAlsa(); + } + } + else + { + LOG(INFO) << "Failed to get chunk\n"; + while (active_ && !stream_->waitForChunk(100)) + { + LOG(DEBUG) << "Waiting for chunk\n"; + if ((handle_ != NULL) && (chronos::getTickCount() - lastChunkTick > 5000)) + { + LOG(NOTICE) << "No chunk received for 5000ms. Closing ALSA.\n"; + uninitAlsa(); + stream_->clearChunks(); + } + } + } + } } vector AlsaPlayer::pcm_list(void) { - void **hints, **n; - char *name, *descr, *io; - vector result; - PcmDevice pcmDevice; + void **hints, **n; + char *name, *descr, *io; + vector result; + PcmDevice pcmDevice; - if (snd_device_name_hint(-1, "pcm", &hints) < 0) - return result; - n = hints; - size_t idx(0); - while (*n != NULL) - { - name = snd_device_name_get_hint(*n, "NAME"); - descr = snd_device_name_get_hint(*n, "DESC"); - io = snd_device_name_get_hint(*n, "IOID"); - if (io != NULL && strcmp(io, "Output") != 0) - goto __end; - pcmDevice.name = name; - if(descr == NULL) { - pcmDevice.description = ""; - } else { - pcmDevice.description = descr; - } - pcmDevice.idx = idx++; - result.push_back(pcmDevice); + if (snd_device_name_hint(-1, "pcm", &hints) < 0) + return result; + n = hints; + size_t idx(0); + while (*n != NULL) + { + name = snd_device_name_get_hint(*n, "NAME"); + descr = snd_device_name_get_hint(*n, "DESC"); + io = snd_device_name_get_hint(*n, "IOID"); + if (io != NULL && strcmp(io, "Output") != 0) + goto __end; + pcmDevice.name = name; + if (descr == NULL) + { + pcmDevice.description = ""; + } + else + { + pcmDevice.description = descr; + } + pcmDevice.idx = idx++; + result.push_back(pcmDevice); -__end: - if (name != NULL) - free(name); - if (descr != NULL) - free(descr); - if (io != NULL) - free(io); - n++; - } - snd_device_name_free_hint(hints); - return result; + __end: + if (name != NULL) + free(name); + if (descr != NULL) + free(descr); + if (io != NULL) + free(io); + n++; + } + snd_device_name_free_hint(hints); + return result; } - diff --git a/client/player/alsaPlayer.h b/client/player/alsaPlayer.h index c12d23b0..9138f706 100644 --- a/client/player/alsaPlayer.h +++ b/client/player/alsaPlayer.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -30,28 +30,27 @@ class AlsaPlayer : public Player { public: - AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream); - virtual ~AlsaPlayer(); + AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream); + virtual ~AlsaPlayer(); - /// Set audio volume in range [0..1] - virtual void start(); - virtual void stop(); + /// Set audio volume in range [0..1] + virtual void start(); + virtual void stop(); - /// List the system's audio output devices - static std::vector pcm_list(void); + /// List the system's audio output devices + static std::vector pcm_list(void); protected: - virtual void worker(); + virtual void worker(); private: - void initAlsa(); - void uninitAlsa(); + void initAlsa(); + void uninitAlsa(); - snd_pcm_t* handle_; - snd_pcm_uframes_t frames_; - char *buff_; + snd_pcm_t* handle_; + snd_pcm_uframes_t frames_; + char* buff_; }; #endif - diff --git a/client/player/coreAudioPlayer.cpp b/client/player/coreAudioPlayer.cpp index 5fc9b819..2f6f3b77 100644 --- a/client/player/coreAudioPlayer.cpp +++ b/client/player/coreAudioPlayer.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -17,26 +17,23 @@ ***/ //#include -#include #include "coreAudioPlayer.h" +#include #define NUM_BUFFERS 2 -//http://stackoverflow.com/questions/4863811/how-to-use-audioqueue-to-play-a-sound-for-mac-osx-in-c -//https://gist.github.com/andormade/1360885 +// http://stackoverflow.com/questions/4863811/how-to-use-audioqueue-to-play-a-sound-for-mac-osx-in-c +// 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(custom_data); - player->playerCallback(queue, buffer); + CoreAudioPlayer* player = static_cast(custom_data); + player->playerCallback(queue, buffer); } -CoreAudioPlayer::CoreAudioPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream) : - Player(pcmDevice, stream), - ms_(100), - pubStream_(stream) +CoreAudioPlayer::CoreAudioPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream) : Player(pcmDevice, stream), ms_(100), pubStream_(stream) { } @@ -49,162 +46,160 @@ CoreAudioPlayer::~CoreAudioPlayer() /// TODO: experimental. No output device can be configured yet. std::vector CoreAudioPlayer::pcm_list(void) { - UInt32 propsize; - - AudioObjectPropertyAddress theAddress = { kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize); - int nDevices = propsize / sizeof(AudioDeviceID); - AudioDeviceID *devids = new AudioDeviceID[nDevices]; - AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids); + UInt32 propsize; - std::vector result; - for (int i = 0; i < nDevices; ++i) - { - if (devids[i] == kAudioDeviceUnknown) - continue; + AudioObjectPropertyAddress theAddress = {kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; - UInt32 propSize; - AudioObjectPropertyAddress theAddress = { kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyScopeOutput, 0 }; - if (AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propSize)) - continue; + AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize); + int nDevices = propsize / sizeof(AudioDeviceID); + AudioDeviceID* devids = new AudioDeviceID[nDevices]; + AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids); - AudioBufferList *buflist = (AudioBufferList *)malloc(propSize); - if (AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propSize, buflist)) - continue; - int channels = 0; - for (UInt32 i = 0; i < buflist->mNumberBuffers; ++i) - channels += buflist->mBuffers[i].mNumberChannels; - free(buflist); - if (channels == 0) - continue; + std::vector result; + for (int i = 0; i < nDevices; ++i) + { + if (devids[i] == kAudioDeviceUnknown) + continue; - UInt32 maxlen = 1024; - char buf[maxlen]; - theAddress = { kAudioDevicePropertyDeviceName, kAudioDevicePropertyScopeOutput, 0 }; - AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &maxlen, buf); - LOG(DEBUG) << "device: " << i << ", name: " << buf << ", channels: " << channels << "\n"; + UInt32 propSize; + AudioObjectPropertyAddress theAddress = {kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyScopeOutput, 0}; + if (AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propSize)) + continue; - result.push_back(PcmDevice(i, buf)); - } - delete[] devids; - return result; + AudioBufferList* buflist = (AudioBufferList*)malloc(propSize); + if (AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propSize, buflist)) + continue; + int channels = 0; + for (UInt32 i = 0; i < buflist->mNumberBuffers; ++i) + channels += buflist->mBuffers[i].mNumberChannels; + free(buflist); + if (channels == 0) + continue; + + UInt32 maxlen = 1024; + char buf[maxlen]; + theAddress = {kAudioDevicePropertyDeviceName, kAudioDevicePropertyScopeOutput, 0}; + AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &maxlen, buf); + LOG(DEBUG) << "device: " << i << ", name: " << buf << ", channels: " << channels << "\n"; + + result.push_back(PcmDevice(i, buf)); + } + delete[] devids; + return result; } void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bufferRef) { - /// Estimate the playout delay by checking the number of frames left in the buffer - /// and add ms_ (= complete buffer size). Based on trying. - AudioTimeStamp timestamp; - AudioQueueGetCurrentTime(queue, timeLine_, ×tamp, NULL); - size_t bufferedFrames = (frames_ - ((uint64_t)timestamp.mSampleTime % frames_)) % frames_; - size_t bufferedMs = bufferedFrames * 1000 / pubStream_->getFormat().rate + (ms_ * (NUM_BUFFERS - 1)); - /// 15ms DAC delay. Based on trying. - bufferedMs += 15; -// LOG(INFO) << "buffered: " << bufferedFrames << ", ms: " << bufferedMs << ", mSampleTime: " << timestamp.mSampleTime << "\n"; + /// Estimate the playout delay by checking the number of frames left in the buffer + /// and add ms_ (= complete buffer size). Based on trying. + AudioTimeStamp timestamp; + AudioQueueGetCurrentTime(queue, timeLine_, ×tamp, NULL); + size_t bufferedFrames = (frames_ - ((uint64_t)timestamp.mSampleTime % frames_)) % frames_; + size_t bufferedMs = bufferedFrames * 1000 / pubStream_->getFormat().rate + (ms_ * (NUM_BUFFERS - 1)); + /// 15ms DAC delay. Based on trying. + bufferedMs += 15; + // 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 - chronos::usec delay(bufferedMs * 1000); - char *buffer = (char*)bufferRef->mAudioData; - if (!pubStream_->getPlayerChunk(buffer, delay, frames_)) - { - if (chronos::getTickCount() - lastChunkTick > 5000) - { - LOG(NOTICE) << "No chunk received for 5000ms. Closing Audio Queue.\n"; - uninitAudioQueue(queue); - return; - } -// LOG(INFO) << "Failed to get chunk. Playing silence.\n"; - memset(buffer, 0, buff_size_); - } - else - { - lastChunkTick = chronos::getTickCount(); - adjustVolume(buffer, frames_); - } + /// 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); + char* buffer = (char*)bufferRef->mAudioData; + if (!pubStream_->getPlayerChunk(buffer, delay, frames_)) + { + if (chronos::getTickCount() - lastChunkTick > 5000) + { + LOG(NOTICE) << "No chunk received for 5000ms. Closing Audio Queue.\n"; + uninitAudioQueue(queue); + return; + } + // LOG(INFO) << "Failed to get chunk. Playing silence.\n"; + memset(buffer, 0, buff_size_); + } + else + { + lastChunkTick = chronos::getTickCount(); + adjustVolume(buffer, frames_); + } -// OSStatus status = - AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL); + // OSStatus status = + AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL); - if (!active_) - { - uninitAudioQueue(queue); - } + if (!active_) + { + uninitAudioQueue(queue); + } } void CoreAudioPlayer::worker() { - while (active_) - { - if (pubStream_->waitForChunk(100)) - { - try - { - initAudioQueue(); - } - catch (const std::exception& e) - { - LOG(ERROR) << "Exception in worker: " << e.what() << "\n"; - chronos::sleep(100); - } - } - chronos::sleep(100); - } + while (active_) + { + if (pubStream_->waitForChunk(100)) + { + try + { + initAudioQueue(); + } + catch (const std::exception& e) + { + LOG(ERROR) << "Exception in worker: " << e.what() << "\n"; + chronos::sleep(100); + } + } + chronos::sleep(100); + } } void CoreAudioPlayer::initAudioQueue() { - const SampleFormat& sampleFormat = pubStream_->getFormat(); + const SampleFormat& sampleFormat = pubStream_->getFormat(); - AudioStreamBasicDescription format; - format.mSampleRate = sampleFormat.rate; - format.mFormatID = kAudioFormatLinearPCM; - format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;// | kAudioFormatFlagIsPacked; - format.mBitsPerChannel = sampleFormat.bits; - format.mChannelsPerFrame = sampleFormat.channels; - format.mBytesPerFrame = sampleFormat.frameSize; - format.mFramesPerPacket = 1; - format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket; - format.mReserved = 0; + AudioStreamBasicDescription format; + format.mSampleRate = sampleFormat.rate; + format.mFormatID = kAudioFormatLinearPCM; + format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; // | kAudioFormatFlagIsPacked; + format.mBitsPerChannel = sampleFormat.bits; + format.mChannelsPerFrame = sampleFormat.channels; + format.mBytesPerFrame = sampleFormat.frameSize; + format.mFramesPerPacket = 1; + format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket; + format.mReserved = 0; - AudioQueueRef queue; - AudioQueueNewOutput(&format, callback, this, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &queue); - AudioQueueCreateTimeline(queue, &timeLine_); + AudioQueueRef queue; + AudioQueueNewOutput(&format, callback, this, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &queue); + AudioQueueCreateTimeline(queue, &timeLine_); - // Apple recommends this as buffer size: - // https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html - // static const int maxBufferSize = 0x10000; // limit maximum size to 64K - // static const int minBufferSize = 0x4000; // limit minimum size to 16K - // - // For 100ms @ 48000:16:2 we have 19.2K - // frames: 4800, ms: 100, buffer size: 19200 - frames_ = (sampleFormat.rate * ms_) / 1000; - ms_ = frames_ * 1000 / sampleFormat.rate; - buff_size_ = frames_ * sampleFormat.frameSize; - LOG(INFO) << "frames: " << frames_ << ", ms: " << ms_ << ", buffer size: " << buff_size_ << "\n"; + // Apple recommends this as buffer size: + // https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html + // static const int maxBufferSize = 0x10000; // limit maximum size to 64K + // static const int minBufferSize = 0x4000; // limit minimum size to 16K + // + // For 100ms @ 48000:16:2 we have 19.2K + // frames: 4800, ms: 100, buffer size: 19200 + frames_ = (sampleFormat.rate * ms_) / 1000; + ms_ = frames_ * 1000 / sampleFormat.rate; + buff_size_ = frames_ * sampleFormat.frameSize; + LOG(INFO) << "frames: " << frames_ << ", ms: " << ms_ << ", buffer size: " << buff_size_ << "\n"; - AudioQueueBufferRef buffers[NUM_BUFFERS]; - for (int i = 0; i < NUM_BUFFERS; i++) - { - AudioQueueAllocateBuffer(queue, buff_size_, &buffers[i]); - buffers[i]->mAudioDataByteSize = buff_size_; - callback(this, queue, buffers[i]); - } + AudioQueueBufferRef buffers[NUM_BUFFERS]; + for (int i = 0; i < NUM_BUFFERS; i++) + { + AudioQueueAllocateBuffer(queue, buff_size_, &buffers[i]); + buffers[i]->mAudioDataByteSize = buff_size_; + callback(this, queue, buffers[i]); + } - LOG(ERROR) << "CoreAudioPlayer::worker\n"; - AudioQueueCreateTimeline(queue, &timeLine_); - AudioQueueStart(queue, NULL); - CFRunLoopRun(); + LOG(ERROR) << "CoreAudioPlayer::worker\n"; + AudioQueueCreateTimeline(queue, &timeLine_); + AudioQueueStart(queue, NULL); + CFRunLoopRun(); } void CoreAudioPlayer::uninitAudioQueue(AudioQueueRef queue) { - AudioQueueStop(queue, false); - AudioQueueDispose(queue, false); - pubStream_->clearChunks(); - CFRunLoopStop(CFRunLoopGetCurrent()); + AudioQueueStop(queue, false); + AudioQueueDispose(queue, false); + pubStream_->clearChunks(); + CFRunLoopStop(CFRunLoopGetCurrent()); } diff --git a/client/player/coreAudioPlayer.h b/client/player/coreAudioPlayer.h index 12cf56f4..f871339b 100644 --- a/client/player/coreAudioPlayer.h +++ b/client/player/coreAudioPlayer.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -28,34 +28,33 @@ /// Audio Player /** * Audio player implementation using CoreAudio - * - * Warning: + * + * Warning: * * !! This player is experimental and might not be maintained !! - * + * */ class CoreAudioPlayer : public Player { public: - CoreAudioPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream); - virtual ~CoreAudioPlayer(); + CoreAudioPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream); + virtual ~CoreAudioPlayer(); - void playerCallback(AudioQueueRef queue, AudioQueueBufferRef bufferRef); - static std::vector pcm_list(void); + void playerCallback(AudioQueueRef queue, AudioQueueBufferRef bufferRef); + static std::vector pcm_list(void); protected: - virtual void worker(); - void initAudioQueue(); - void uninitAudioQueue(AudioQueueRef queue); + virtual void worker(); + void initAudioQueue(); + void uninitAudioQueue(AudioQueueRef queue); - AudioQueueTimelineRef timeLine_; - size_t ms_; - size_t frames_; - size_t buff_size_; - std::shared_ptr pubStream_; - long lastChunkTick; + AudioQueueTimelineRef timeLine_; + size_t ms_; + size_t frames_; + size_t buff_size_; + std::shared_ptr pubStream_; + long lastChunkTick; }; #endif - diff --git a/client/player/openslPlayer.cpp b/client/player/openslPlayer.cpp index f2d66f6d..5874c023 100644 --- a/client/player/openslPlayer.cpp +++ b/client/player/openslPlayer.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,10 +19,10 @@ #include #include -#include "openslPlayer.h" #include "aixlog.hpp" #include "common/snapException.h" #include "common/strCompat.h" +#include "openslPlayer.h" using namespace std; @@ -35,382 +35,361 @@ using namespace std; // 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, // 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(context); - player->playerCallback(bq); + OpenslPlayer* player = static_cast(context); + player->playerCallback(bq); } -OpenslPlayer::OpenslPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream) : - Player(pcmDevice, stream), - engineObject(NULL), - engineEngine(NULL), - outputMixObject(NULL), - bqPlayerObject(NULL), - bqPlayerPlay(NULL), - bqPlayerBufferQueue(NULL), - bqPlayerVolume(NULL), - curBuffer(0), - ms_(50), - buff_size(0), - pubStream_(stream) +OpenslPlayer::OpenslPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream) + : Player(pcmDevice, stream), engineObject(NULL), engineEngine(NULL), outputMixObject(NULL), bqPlayerObject(NULL), bqPlayerPlay(NULL), + bqPlayerBufferQueue(NULL), bqPlayerVolume(NULL), curBuffer(0), ms_(50), buff_size(0), pubStream_(stream) { - initOpensl(); + initOpensl(); } OpenslPlayer::~OpenslPlayer() { - uninitOpensl(); + uninitOpensl(); } void OpenslPlayer::playerCallback(SLAndroidSimpleBufferQueueItf bq) { - if (bq != bqPlayerBufferQueue) - { - LOG(ERROR) << "Wrong bq!\n"; - return; - } + if (bq != bqPlayerBufferQueue) + { + LOG(ERROR) << "Wrong bq!\n"; + return; + } -/* static long lastTick = 0; - long now = chronos::getTickCount(); - int diff = 0; - if (lastTick != 0) - { - diff = now - lastTick; -// LOG(ERROR) << "diff: " << diff << ", frames: " << player->frames_ / 44.1 << "\n"; -// if (diff <= 50) -// { -// usleep(1000 * (50 - diff)); -// diff = 50; -// } - } - lastTick = chronos::getTickCount(); -*/ + /* static long lastTick = 0; + long now = chronos::getTickCount(); + int diff = 0; + if (lastTick != 0) + { + diff = now - lastTick; + // LOG(ERROR) << "diff: " << diff << ", frames: " << player->frames_ / 44.1 << "\n"; + // if (diff <= 50) + // { + // usleep(1000 * (50 - diff)); + // diff = 50; + // } + } + lastTick = chronos::getTickCount(); + */ -// size_t d = player->frames_ / 0.48d; -// LOG(ERROR) << "Delay: " << d << "\n"; -// SLAndroidSimpleBufferQueueState state; -// (*bq)->GetState(bq, &state); -// cout << "bqPlayerCallback count: " << state.count << ", idx: " << state.index << "\n"; + // size_t d = player->frames_ / 0.48d; + // LOG(ERROR) << "Delay: " << d << "\n"; + // SLAndroidSimpleBufferQueueState state; + // (*bq)->GetState(bq, &state); + // cout << "bqPlayerCallback count: " << state.count << ", idx: " << state.index << "\n"; -// diff = 0; -// chronos::usec delay((250 - diff) * 1000); + // diff = 0; + // chronos::usec delay((250 - diff) * 1000); -// while (active_ && !stream_->waitForChunk(100)) -// LOG(INFO) << "Waiting for chunk\n"; + // while (active_ && !stream_->waitForChunk(100)) + // LOG(INFO) << "Waiting for chunk\n"; - if (!active_) - return; + if (!active_) + return; - chronos::usec delay(ms_ * 1000); - if (!pubStream_->getPlayerChunk(buffer[curBuffer], delay, frames_)) - { -// LOG(INFO) << "Failed to get chunk. Playing silence.\n"; - memset(buffer[curBuffer], 0, buff_size); - } - else - { - adjustVolume(buffer[curBuffer], frames_); - } + chronos::usec delay(ms_ * 1000); + if (!pubStream_->getPlayerChunk(buffer[curBuffer], delay, frames_)) + { + // LOG(INFO) << "Failed to get chunk. Playing silence.\n"; + memset(buffer[curBuffer], 0, buff_size); + } + else + { + adjustVolume(buffer[curBuffer], frames_); + } - while (active_) - { - SLresult result = (*bq)->Enqueue(bq, buffer[curBuffer], buff_size); - if (result == SL_RESULT_BUFFER_INSUFFICIENT) - chronos::sleep(1); - else - break; - } + while (active_) + { + SLresult result = (*bq)->Enqueue(bq, buffer[curBuffer], buff_size); + if (result == SL_RESULT_BUFFER_INSUFFICIENT) + chronos::sleep(1); + else + break; + } - curBuffer ^= 1; // Switch buffer + curBuffer ^= 1; // Switch buffer } std::string OpenslPlayer::resultToString(SLresult result) const { - switch(result) - { - case SL_RESULT_SUCCESS: - return "SL_RESULT_SUCCESS"; - case SL_RESULT_PRECONDITIONS_VIOLATED: - return "SL_RESULT_PRECONDITIONS_VIOLATED"; - case SL_RESULT_PARAMETER_INVALID: - return "SL_RESULT_PARAMETER_INVALID"; - case SL_RESULT_MEMORY_FAILURE: - return "SL_RESULT_MEMORY_FAILURE"; - case SL_RESULT_RESOURCE_ERROR: - return "SL_RESULT_RESOURCE_ERROR"; - case SL_RESULT_RESOURCE_LOST: - return "SL_RESULT_RESOURCE_LOST"; - case SL_RESULT_IO_ERROR: - return "SL_RESULT_IO_ERROR"; - case SL_RESULT_BUFFER_INSUFFICIENT: - return "SL_RESULT_BUFFER_INSUFFICIENT"; - case SL_RESULT_CONTENT_CORRUPTED: - return "SL_RESULT_CONTENT_CORRUPTED"; - case SL_RESULT_CONTENT_UNSUPPORTED: - return "SL_RESULT_CONTENT_UNSUPPORTED"; - case SL_RESULT_CONTENT_NOT_FOUND: - return "SL_RESULT_CONTENT_NOT_FOUND"; - case SL_RESULT_PERMISSION_DENIED: - return "SL_RESULT_PERMISSION_DENIED"; - case SL_RESULT_FEATURE_UNSUPPORTED: - return "SL_RESULT_FEATURE_UNSUPPORTED"; - case SL_RESULT_INTERNAL_ERROR: - return "SL_RESULT_INTERNAL_ERROR"; - case SL_RESULT_UNKNOWN_ERROR: - return "SL_RESULT_UNKNOWN_ERROR"; - case SL_RESULT_OPERATION_ABORTED: - return "SL_RESULT_OPERATION_ABORTED"; - case SL_RESULT_CONTROL_LOST: - return "SL_RESULT_CONTROL_LOST"; - default: - return "UNKNOWN"; - } + switch (result) + { + case SL_RESULT_SUCCESS: + return "SL_RESULT_SUCCESS"; + case SL_RESULT_PRECONDITIONS_VIOLATED: + return "SL_RESULT_PRECONDITIONS_VIOLATED"; + case SL_RESULT_PARAMETER_INVALID: + return "SL_RESULT_PARAMETER_INVALID"; + case SL_RESULT_MEMORY_FAILURE: + return "SL_RESULT_MEMORY_FAILURE"; + case SL_RESULT_RESOURCE_ERROR: + return "SL_RESULT_RESOURCE_ERROR"; + case SL_RESULT_RESOURCE_LOST: + return "SL_RESULT_RESOURCE_LOST"; + case SL_RESULT_IO_ERROR: + return "SL_RESULT_IO_ERROR"; + case SL_RESULT_BUFFER_INSUFFICIENT: + return "SL_RESULT_BUFFER_INSUFFICIENT"; + case SL_RESULT_CONTENT_CORRUPTED: + return "SL_RESULT_CONTENT_CORRUPTED"; + case SL_RESULT_CONTENT_UNSUPPORTED: + return "SL_RESULT_CONTENT_UNSUPPORTED"; + case SL_RESULT_CONTENT_NOT_FOUND: + return "SL_RESULT_CONTENT_NOT_FOUND"; + case SL_RESULT_PERMISSION_DENIED: + return "SL_RESULT_PERMISSION_DENIED"; + case SL_RESULT_FEATURE_UNSUPPORTED: + return "SL_RESULT_FEATURE_UNSUPPORTED"; + case SL_RESULT_INTERNAL_ERROR: + return "SL_RESULT_INTERNAL_ERROR"; + case SL_RESULT_UNKNOWN_ERROR: + return "SL_RESULT_UNKNOWN_ERROR"; + case SL_RESULT_OPERATION_ABORTED: + return "SL_RESULT_OPERATION_ABORTED"; + case SL_RESULT_CONTROL_LOST: + return "SL_RESULT_CONTROL_LOST"; + default: + return "UNKNOWN"; + } } void OpenslPlayer::throwUnsuccess(const std::string& what, SLresult result) { - if (SL_RESULT_SUCCESS == result) - return; - stringstream ss; - ss << what << " failed: " << resultToString(result) << "(" << result << ")"; - throw SnapException(ss.str()); + if (SL_RESULT_SUCCESS == result) + return; + stringstream ss; + ss << what << " failed: " << resultToString(result) << "(" << result << ")"; + throw SnapException(ss.str()); } void OpenslPlayer::initOpensl() { - if (active_) - return; + if (active_) + return; - 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 */; - LOG(INFO) << "frames: " << frames_ << ", channels: " << format.channels << ", rate: " << format.rate << ", buff: " << buff_size << "\n"; + buff_size = frames_ * format.frameSize /* 2 -> sample size */; + LOG(INFO) << "frames: " << frames_ << ", channels: " << format.channels << ", rate: " << format.rate << ", buff: " << buff_size << "\n"; - SLresult result; - // create engine - SLEngineOption engineOption[] = - { - {(SLuint32) SL_ENGINEOPTION_THREADSAFE, (SLuint32) SL_BOOLEAN_TRUE} - }; - result = slCreateEngine(&engineObject, 1, engineOption, 0, NULL, NULL); - throwUnsuccess("slCreateEngine", result); - result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); - throwUnsuccess("EngineObject::Realize", result); - result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); - throwUnsuccess("EngineObject::GetInterface", result); - result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0); - throwUnsuccess("EngineEngine::CreateOutputMix", result); - result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); - throwUnsuccess("OutputMixObject::Realize", result); + SLresult result; + // create engine + SLEngineOption engineOption[] = {{(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}}; + result = slCreateEngine(&engineObject, 1, engineOption, 0, NULL, NULL); + throwUnsuccess("slCreateEngine", result); + result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); + throwUnsuccess("EngineObject::Realize", result); + result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); + throwUnsuccess("EngineObject::GetInterface", result); + result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0); + throwUnsuccess("EngineEngine::CreateOutputMix", result); + result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); + throwUnsuccess("OutputMixObject::Realize", result); - SLuint32 samplesPerSec = SL_SAMPLINGRATE_48; - switch(format.rate) - { - case 8000: - samplesPerSec = SL_SAMPLINGRATE_8; - break; - case 11025: - samplesPerSec = SL_SAMPLINGRATE_11_025; - break; - case 16000: - samplesPerSec = SL_SAMPLINGRATE_16; - break; - case 22050: - samplesPerSec = SL_SAMPLINGRATE_22_05; - break; - case 24000: - samplesPerSec = SL_SAMPLINGRATE_24; - break; - case 32000: - samplesPerSec = SL_SAMPLINGRATE_32; - break; - case 44100: - samplesPerSec = SL_SAMPLINGRATE_44_1; - break; - case 48000: - samplesPerSec = SL_SAMPLINGRATE_48; - break; - case 64000: - samplesPerSec = SL_SAMPLINGRATE_64; - break; - case 88200: - samplesPerSec = SL_SAMPLINGRATE_88_2; - break; - case 96000: - samplesPerSec = SL_SAMPLINGRATE_96; - break; - case 192000: - samplesPerSec = SL_SAMPLINGRATE_192; - break; - default: - throw SnapException("Sample rate not supported"); - } + SLuint32 samplesPerSec = SL_SAMPLINGRATE_48; + switch (format.rate) + { + case 8000: + samplesPerSec = SL_SAMPLINGRATE_8; + break; + case 11025: + samplesPerSec = SL_SAMPLINGRATE_11_025; + break; + case 16000: + samplesPerSec = SL_SAMPLINGRATE_16; + break; + case 22050: + samplesPerSec = SL_SAMPLINGRATE_22_05; + break; + case 24000: + samplesPerSec = SL_SAMPLINGRATE_24; + break; + case 32000: + samplesPerSec = SL_SAMPLINGRATE_32; + break; + case 44100: + samplesPerSec = SL_SAMPLINGRATE_44_1; + break; + case 48000: + samplesPerSec = SL_SAMPLINGRATE_48; + break; + case 64000: + samplesPerSec = SL_SAMPLINGRATE_64; + break; + case 88200: + samplesPerSec = SL_SAMPLINGRATE_88_2; + break; + case 96000: + samplesPerSec = SL_SAMPLINGRATE_96; + break; + case 192000: + samplesPerSec = SL_SAMPLINGRATE_192; + break; + default: + throw SnapException("Sample rate not supported"); + } - SLuint32 bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - SLuint32 containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - switch(format.bits) - { - case 8: - bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8; - containerSize = SL_PCMSAMPLEFORMAT_FIXED_8; - break; - case 16: - bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - break; - case 24: - bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_24; - containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; - break; - case 32: - bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; - containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; - break; - default: - throw SnapException("Unsupported sample format: " + cpt::to_string(format.bits)); - } - - - SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; - SLDataFormat_PCM format_pcm = - { - SL_DATAFORMAT_PCM, - format.channels, - samplesPerSec, - bitsPerSample, - containerSize, - SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, - SL_BYTEORDER_LITTLEENDIAN - }; + SLuint32 bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + SLuint32 containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + switch (format.bits) + { + case 8: + bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8; + containerSize = SL_PCMSAMPLEFORMAT_FIXED_8; + break; + case 16: + bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + break; + case 24: + bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_24; + containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; + break; + case 32: + bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; + containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; + break; + default: + throw SnapException("Unsupported sample format: " + cpt::to_string(format.bits)); + } - SLDataSource audioSrc = {&loc_bufq, &format_pcm}; - // configure audio sink - SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; - SLDataSink audioSnk = {&loc_outmix, NULL}; + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, format.channels, samplesPerSec, bitsPerSample, containerSize, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, + SL_BYTEORDER_LITTLEENDIAN}; - // create audio player - 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}; - result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 3, ids, req); - throwUnsuccess("Engine::CreateAudioPlayer", result); + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; - SLAndroidConfigurationItf playerConfig; - result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDCONFIGURATION, &playerConfig); - throwUnsuccess("PlayerObject::GetInterface", result); - SLint32 streamType = SL_ANDROID_STREAM_MEDIA; -//// SLint32 streamType = SL_ANDROID_STREAM_VOICE; - result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32)); - throwUnsuccess("PlayerConfig::SetConfiguration", result); + // configure audio sink + SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; + SLDataSink audioSnk = {&loc_outmix, NULL}; - result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); - throwUnsuccess("PlayerObject::Realize", result); - result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); - throwUnsuccess("PlayerObject::GetInterface", result); - result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue); - throwUnsuccess("PlayerObject::GetInterface", result); - result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this); - throwUnsuccess("PlayerBufferQueue::RegisterCallback", result); -// result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); -// throwUnsuccess("PlayerObject::GetInterface", result); - result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED); - throwUnsuccess("PlayerPlay::SetPlayState", result); + // create audio player + 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}; + result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 3, ids, req); + throwUnsuccess("Engine::CreateAudioPlayer", result); - // Render and enqueue a first buffer. (or should we just play the buffer empty?) - curBuffer = 0; - buffer[0] = new char[buff_size]; - buffer[1] = new char[buff_size]; + SLAndroidConfigurationItf playerConfig; + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDCONFIGURATION, &playerConfig); + throwUnsuccess("PlayerObject::GetInterface", result); + SLint32 streamType = SL_ANDROID_STREAM_MEDIA; + //// SLint32 streamType = SL_ANDROID_STREAM_VOICE; + result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32)); + throwUnsuccess("PlayerConfig::SetConfiguration", result); - active_ = true; + result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); + throwUnsuccess("PlayerObject::Realize", result); + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); + throwUnsuccess("PlayerObject::GetInterface", result); + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue); + throwUnsuccess("PlayerObject::GetInterface", result); + result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this); + throwUnsuccess("PlayerBufferQueue::RegisterCallback", result); + // result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); + // throwUnsuccess("PlayerObject::GetInterface", result); + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED); + throwUnsuccess("PlayerPlay::SetPlayState", result); - memset(buffer[curBuffer], 0, buff_size); - result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeof(buffer[curBuffer])); - throwUnsuccess("PlayerBufferQueue::Enqueue", result); - curBuffer ^= 1; + // Render and enqueue a first buffer. (or should we just play the buffer empty?) + curBuffer = 0; + buffer[0] = new char[buff_size]; + buffer[1] = new char[buff_size]; + + active_ = true; + + memset(buffer[curBuffer], 0, buff_size); + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer[curBuffer], sizeof(buffer[curBuffer])); + throwUnsuccess("PlayerBufferQueue::Enqueue", result); + curBuffer ^= 1; } void OpenslPlayer::uninitOpensl() { -// if (!active_) -// return; + // if (!active_) + // return; - LOG(INFO) << "uninitOpensl\n"; - SLresult result; - LOG(INFO) << "OpenSLWrap_Shutdown - stopping playback\n"; - if (bqPlayerPlay != NULL) - { - result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); - if (SL_RESULT_SUCCESS != result) - LOG(ERROR) << "SetPlayState failed\n"; - } + LOG(INFO) << "uninitOpensl\n"; + SLresult result; + LOG(INFO) << "OpenSLWrap_Shutdown - stopping playback\n"; + if (bqPlayerPlay != NULL) + { + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); + if (SL_RESULT_SUCCESS != result) + LOG(ERROR) << "SetPlayState failed\n"; + } - LOG(INFO) << "OpenSLWrap_Shutdown - deleting player object\n"; + LOG(INFO) << "OpenSLWrap_Shutdown - deleting player object\n"; - if (bqPlayerObject != NULL) - { - (*bqPlayerObject)->Destroy(bqPlayerObject); - bqPlayerObject = NULL; - bqPlayerPlay = NULL; - bqPlayerBufferQueue = NULL; - bqPlayerVolume = NULL; - } + if (bqPlayerObject != NULL) + { + (*bqPlayerObject)->Destroy(bqPlayerObject); + bqPlayerObject = NULL; + bqPlayerPlay = NULL; + bqPlayerBufferQueue = NULL; + bqPlayerVolume = NULL; + } - LOG(INFO) << "OpenSLWrap_Shutdown - deleting mix object\n"; + LOG(INFO) << "OpenSLWrap_Shutdown - deleting mix object\n"; - if (outputMixObject != NULL) - { - (*outputMixObject)->Destroy(outputMixObject); - outputMixObject = NULL; - } + if (outputMixObject != NULL) + { + (*outputMixObject)->Destroy(outputMixObject); + outputMixObject = NULL; + } - LOG(INFO) << "OpenSLWrap_Shutdown - deleting engine object\n"; + LOG(INFO) << "OpenSLWrap_Shutdown - deleting engine object\n"; - if (engineObject != NULL) - { - (*engineObject)->Destroy(engineObject); - engineObject = NULL; - engineEngine = NULL; - } + if (engineObject != NULL) + { + (*engineObject)->Destroy(engineObject); + engineObject = NULL; + engineEngine = NULL; + } - delete [] buffer[0]; - buffer[0] = NULL; + delete[] buffer[0]; + buffer[0] = NULL; - delete [] buffer[1]; - buffer[1] = NULL; + delete[] buffer[1]; + buffer[1] = NULL; - LOG(INFO) << "OpenSLWrap_Shutdown - finished\n"; - active_ = false; + LOG(INFO) << "OpenSLWrap_Shutdown - finished\n"; + active_ = false; } void OpenslPlayer::start() { - SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); - throwUnsuccess("PlayerPlay::SetPlayState", result); + SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); + throwUnsuccess("PlayerPlay::SetPlayState", result); } void OpenslPlayer::stop() { - SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); - throwUnsuccess("PlayerPlay::SetPlayState", result); + SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); + throwUnsuccess("PlayerPlay::SetPlayState", result); } void OpenslPlayer::worker() { } - diff --git a/client/player/openslPlayer.h b/client/player/openslPlayer.h index efac473c..78de66f7 100644 --- a/client/player/openslPlayer.h +++ b/client/player/openslPlayer.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -25,7 +25,7 @@ #include "player.h" -typedef int (*AndroidAudioCallback)(short *buffer, int num_samples); +typedef int (*AndroidAudioCallback)(short* buffer, int num_samples); /// OpenSL Audio Player @@ -35,43 +35,42 @@ typedef int (*AndroidAudioCallback)(short *buffer, int num_samples); class OpenslPlayer : public Player { public: - OpenslPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream); - virtual ~OpenslPlayer(); + OpenslPlayer(const PcmDevice& pcmDevice, std::shared_ptr stream); + virtual ~OpenslPlayer(); - virtual void start(); - virtual void stop(); + virtual void start(); + virtual void stop(); - void playerCallback(SLAndroidSimpleBufferQueueItf bq); + void playerCallback(SLAndroidSimpleBufferQueueItf bq); protected: - void initOpensl(); - void uninitOpensl(); + void initOpensl(); + void uninitOpensl(); - virtual void worker(); - void throwUnsuccess(const std::string& what, SLresult result); - std::string resultToString(SLresult result) const; + virtual void worker(); + void throwUnsuccess(const std::string& what, SLresult result); + std::string resultToString(SLresult result) const; - // engine interfaces - SLObjectItf engineObject; - SLEngineItf engineEngine; - SLObjectItf outputMixObject; + // engine interfaces + SLObjectItf engineObject; + SLEngineItf engineEngine; + SLObjectItf outputMixObject; - // buffer queue player interfaces - SLObjectItf bqPlayerObject; - SLPlayItf bqPlayerPlay; - SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; - SLVolumeItf bqPlayerVolume; + // buffer queue player interfaces + SLObjectItf bqPlayerObject; + SLPlayItf bqPlayerPlay; + SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; + SLVolumeItf bqPlayerVolume; - // Double buffering. - int curBuffer; - char *buffer[2]; + // Double buffering. + int curBuffer; + char* buffer[2]; - size_t ms_; - size_t frames_; - size_t buff_size; - std::shared_ptr pubStream_; + size_t ms_; + size_t frames_; + size_t buff_size; + std::shared_ptr pubStream_; }; #endif - diff --git a/client/player/pcmDevice.h b/client/player/pcmDevice.h index d803f386..0c493337 100644 --- a/client/player/pcmDevice.h +++ b/client/player/pcmDevice.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -24,21 +24,14 @@ struct PcmDevice { - PcmDevice() : - idx(-1), name("default") - { - }; - - PcmDevice(int idx, const std::string& name, const std::string& description = "") : - idx(idx), name(name), description(description) - { - }; + PcmDevice() : idx(-1), name("default"){}; - int idx; - std::string name; - std::string description; + PcmDevice(int idx, const std::string& name, const std::string& description = "") : idx(idx), name(name), description(description){}; + + int idx; + std::string name; + std::string description; }; #endif - diff --git a/client/player/player.cpp b/client/player/player.cpp index 941a0eab..e94d6062 100644 --- a/client/player/player.cpp +++ b/client/player/player.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,23 +16,18 @@ along with this program. If not, see . ***/ -#include #include +#include -#include "player.h" #include "aixlog.hpp" +#include "player.h" using namespace std; -Player::Player(const PcmDevice& pcmDevice, std::shared_ptr stream) : - active_(false), - stream_(stream), - pcmDevice_(pcmDevice), - volume_(1.0), - muted_(false), - volCorrection_(1.0) +Player::Player(const PcmDevice& pcmDevice, std::shared_ptr stream) + : active_(false), stream_(stream), pcmDevice_(pcmDevice), volume_(1.0), muted_(false), volCorrection_(1.0) { } @@ -40,78 +35,76 @@ Player::Player(const PcmDevice& pcmDevice, std::shared_ptr stream) : void Player::start() { - active_ = true; - playerThread_ = thread(&Player::worker, this); + active_ = true; + playerThread_ = thread(&Player::worker, this); } Player::~Player() { - stop(); + stop(); } void Player::stop() { - if (active_) - { - active_ = false; - playerThread_.join(); - } + if (active_) + { + active_ = false; + playerThread_.join(); + } } void Player::adjustVolume(char* buffer, size_t frames) { - double volume = volume_; - if (muted_) - volume = 0.; + double volume = volume_; + if (muted_) + volume = 0.; - const SampleFormat& sampleFormat = stream_->getFormat(); + const SampleFormat& sampleFormat = stream_->getFormat(); - if ((volume < 1.0) || (volCorrection_ != 1.)) - { - volume *= volCorrection_; - if (sampleFormat.sampleSize == 1) - adjustVolume(buffer, frames*sampleFormat.channels, volume); - else if (sampleFormat.sampleSize == 2) - adjustVolume(buffer, frames*sampleFormat.channels, volume); - else if (sampleFormat.sampleSize == 4) - adjustVolume(buffer, frames*sampleFormat.channels, volume); - } + if ((volume < 1.0) || (volCorrection_ != 1.)) + { + volume *= volCorrection_; + if (sampleFormat.sampleSize == 1) + adjustVolume(buffer, frames * sampleFormat.channels, volume); + else if (sampleFormat.sampleSize == 2) + adjustVolume(buffer, frames * sampleFormat.channels, volume); + else if (sampleFormat.sampleSize == 4) + adjustVolume(buffer, frames * sampleFormat.channels, volume); + } } -//https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/src/pulse/volume.c#n260 -//http://www.robotplanet.dk/audio/audio_gui_design/ -//https://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#22198 +// https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/src/pulse/volume.c#n260 +// http://www.robotplanet.dk/audio/audio_gui_design/ +// https://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#22198 void Player::setVolume_poly(double volume, double exp) { - volume_ = std::pow(volume, exp); - LOG(DEBUG) << "setVolume poly: " << volume << " => " << volume_ << "\n"; + volume_ = std::pow(volume, exp); + LOG(DEBUG) << "setVolume poly: " << volume << " => " << volume_ << "\n"; } -//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) { -// double base = M_E; -// double base = 10.; - volume_ = (pow(base, volume)-1) / (base-1); - LOG(DEBUG) << "setVolume exp: " << volume << " => " << volume_ << "\n"; + // double base = M_E; + // double base = 10.; + volume_ = (pow(base, volume) - 1) / (base - 1); + LOG(DEBUG) << "setVolume exp: " << volume << " => " << volume_ << "\n"; } void Player::setVolume(double volume) { - setVolume_exp(volume, 10.); + setVolume_exp(volume, 10.); } void Player::setMute(bool mute) { - muted_ = mute; + muted_ = mute; } - - diff --git a/client/player/player.h b/client/player/player.h index f30aa16a..8a9081e7 100644 --- a/client/player/player.h +++ b/client/player/player.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,14 +19,14 @@ #ifndef PLAYER_H #define PLAYER_H +#include "aixlog.hpp" +#include "common/endian.hpp" +#include "pcmDevice.h" +#include "stream.h" +#include #include #include -#include #include -#include "stream.h" -#include "pcmDevice.h" -#include "common/endian.hpp" -#include "aixlog.hpp" /// Audio Player @@ -36,40 +36,39 @@ class Player { public: - Player(const PcmDevice& pcmDevice, std::shared_ptr stream); - virtual ~Player(); + Player(const PcmDevice& pcmDevice, std::shared_ptr stream); + virtual ~Player(); - /// Set audio volume in range [0..1] - virtual void setVolume(double volume); - virtual void setMute(bool mute); - virtual void start(); - virtual void stop(); + /// Set audio volume in range [0..1] + virtual void setVolume(double volume); + virtual void setMute(bool mute); + virtual void start(); + virtual void stop(); protected: - virtual void worker() = 0; + virtual void worker() = 0; - void setVolume_poly(double volume, double exp); - void setVolume_exp(double volume, double base); + void setVolume_poly(double volume, double exp); + void setVolume_exp(double volume, double base); - template - void adjustVolume(char *buffer, size_t count, double volume) - { - T* bufferT = (T*)buffer; - for (size_t n=0; n(endian::swap(bufferT[n]) * volume); - } + template + void adjustVolume(char* buffer, size_t count, double volume) + { + T* bufferT = (T*)buffer; + for (size_t n = 0; n < count; ++n) + bufferT[n] = endian::swap(endian::swap(bufferT[n]) * volume); + } - void adjustVolume(char* buffer, size_t frames); + void adjustVolume(char* buffer, size_t frames); - std::atomic active_; - std::shared_ptr stream_; - std::thread playerThread_; - PcmDevice pcmDevice_; - double volume_; - bool muted_; - double volCorrection_; + std::atomic active_; + std::shared_ptr stream_; + std::thread playerThread_; + PcmDevice pcmDevice_; + double volume_; + bool muted_; + double volCorrection_; }; #endif - diff --git a/client/snapClient.cpp b/client/snapClient.cpp index ce99655f..d49bf6ca 100644 --- a/client/snapClient.cpp +++ b/client/snapClient.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,9 +19,9 @@ #include #include -#include "popl.hpp" -#include "controller.h" #include "browseZeroConf/browsemDNS.h" +#include "controller.h" +#include "popl.hpp" #ifdef HAS_ALSA #include "player/alsaPlayer.h" @@ -44,229 +44,227 @@ volatile sig_atomic_t g_terminated = false; PcmDevice getPcmDevice(const std::string& soundcard) { #ifdef HAS_ALSA - vector pcmDevices = AlsaPlayer::pcm_list(); + vector pcmDevices = AlsaPlayer::pcm_list(); - try - { - int soundcardIdx = cpt::stoi(soundcard); - for (auto dev: pcmDevices) - if (dev.idx == soundcardIdx) - return dev; - } - catch(...) - { - } + try + { + int soundcardIdx = cpt::stoi(soundcard); + for (auto dev : pcmDevices) + if (dev.idx == soundcardIdx) + return dev; + } + catch (...) + { + } - for (auto dev: pcmDevices) - if (dev.name.find(soundcard) != string::npos) - return dev; + for (auto dev : pcmDevices) + if (dev.name.find(soundcard) != string::npos) + return dev; #endif - PcmDevice pcmDevice; - return pcmDevice; + PcmDevice pcmDevice; + return pcmDevice; } -int main (int argc, char **argv) +int main(int argc, char** argv) { #ifdef MACOS #pragma message "Warning: the macOS support is experimental and might not be maintained" #endif - int exitcode = EXIT_SUCCESS; - try - { - string meta_script(""); - string soundcard("default"); - string host(""); - size_t port(1704); - int latency(0); - size_t instance(1); + int exitcode = EXIT_SUCCESS; + try + { + string meta_script(""); + string soundcard("default"); + string host(""); + size_t port(1704); + int latency(0); + size_t instance(1); - OptionParser op("Allowed options"); - auto helpSwitch = op.add("", "help", "produce help message"); - auto groffSwitch = op.add("", "groff", "produce groff message"); - auto debugOption = op.add, Attribute::hidden>("", "debug", "enable debug logging", ""); - auto versionSwitch = op.add("v", "version", "show version number"); + OptionParser op("Allowed options"); + auto helpSwitch = op.add("", "help", "produce help message"); + auto groffSwitch = op.add("", "groff", "produce groff message"); + auto debugOption = op.add, Attribute::hidden>("", "debug", "enable debug logging", ""); + auto versionSwitch = op.add("v", "version", "show version number"); #if defined(HAS_ALSA) - auto listSwitch = op.add("l", "list", "list pcm devices"); - /*auto soundcardValue =*/ op.add>("s", "soundcard", "index or name of the soundcard", "default", &soundcard); + auto listSwitch = op.add("l", "list", "list pcm devices"); + /*auto soundcardValue =*/op.add>("s", "soundcard", "index or name of the soundcard", "default", &soundcard); #endif - auto metaStderr = op.add("e", "mstderr", "send metadata to stderr"); - //auto metaHook = op.add>("m", "mhook", "script to call on meta tags", "", &meta_script); - /*auto hostValue =*/ op.add>("h", "host", "server hostname or ip address", "", &host); - /*auto portValue =*/ op.add>("p", "port", "server port", 1704, &port); + auto metaStderr = op.add("e", "mstderr", "send metadata to stderr"); + // auto metaHook = op.add>("m", "mhook", "script to call on meta tags", "", &meta_script); + /*auto hostValue =*/op.add>("h", "host", "server hostname or ip address", "", &host); + /*auto portValue =*/op.add>("p", "port", "server port", 1704, &port); #ifdef HAS_DAEMON - int processPriority(-3); - auto daemonOption = op.add>("d", "daemon", "daemonize, optional process priority [-20..19]", -3, &processPriority); - auto userValue = op.add>("", "user", "the user[:group] to run snapclient as when daemonized"); + int processPriority(-3); + auto daemonOption = op.add>("d", "daemon", "daemonize, optional process priority [-20..19]", -3, &processPriority); + auto userValue = op.add>("", "user", "the user[:group] to run snapclient as when daemonized"); #endif - /*auto latencyValue =*/ op.add>("", "latency", "latency of the soundcard", 0, &latency); - /*auto instanceValue =*/ op.add>("i", "instance", "instance id", 1, &instance); - auto hostIdValue = op.add>("", "hostID", "unique host id", ""); + /*auto latencyValue =*/op.add>("", "latency", "latency of the soundcard", 0, &latency); + /*auto instanceValue =*/op.add>("i", "instance", "instance id", 1, &instance); + auto hostIdValue = op.add>("", "hostID", "unique host id", ""); - try - { - op.parse(argc, argv); - } - catch (const std::invalid_argument& e) - { - cerr << "Exception: " << e.what() << std::endl; - cout << "\n" << op << "\n"; - exit(EXIT_FAILURE); - } + try + { + op.parse(argc, argv); + } + catch (const std::invalid_argument& e) + { + cerr << "Exception: " << e.what() << std::endl; + cout << "\n" << op << "\n"; + exit(EXIT_FAILURE); + } - if (versionSwitch->is_set()) - { - cout << "snapclient v" << VERSION << "\n" - << "Copyright (C) 2014-2018 BadAix (snapcast@badaix.de).\n" - << "License GPLv3+: GNU GPL version 3 or later .\n" - << "This is free software: you are free to change and redistribute it.\n" - << "There is NO WARRANTY, to the extent permitted by law.\n\n" - << "Written by Johannes M. Pohl.\n\n"; - exit(EXIT_SUCCESS); - } + if (versionSwitch->is_set()) + { + cout << "snapclient v" << VERSION << "\n" + << "Copyright (C) 2014-2018 BadAix (snapcast@badaix.de).\n" + << "License GPLv3+: GNU GPL version 3 or later .\n" + << "This is free software: you are free to change and redistribute it.\n" + << "There is NO WARRANTY, to the extent permitted by law.\n\n" + << "Written by Johannes M. Pohl.\n\n"; + exit(EXIT_SUCCESS); + } #ifdef HAS_ALSA - if (listSwitch->is_set()) - { - vector pcmDevices = AlsaPlayer::pcm_list(); - for (auto dev: pcmDevices) - { - cout << dev.idx << ": " << dev.name << "\n" - << dev.description << "\n\n"; - } - exit(EXIT_SUCCESS); - } + if (listSwitch->is_set()) + { + vector pcmDevices = AlsaPlayer::pcm_list(); + for (auto dev : pcmDevices) + { + cout << dev.idx << ": " << dev.name << "\n" << dev.description << "\n\n"; + } + exit(EXIT_SUCCESS); + } #endif - if (helpSwitch->is_set()) - { - cout << op << "\n"; - exit(EXIT_SUCCESS); - } + if (helpSwitch->is_set()) + { + cout << op << "\n"; + exit(EXIT_SUCCESS); + } - if (groffSwitch->is_set()) - { - GroffOptionPrinter option_printer(&op); - cout << option_printer.print(); - exit(EXIT_SUCCESS); - } + if (groffSwitch->is_set()) + { + GroffOptionPrinter option_printer(&op); + cout << option_printer.print(); + exit(EXIT_SUCCESS); + } - if (instance <= 0) - std::invalid_argument("instance id must be >= 1"); + if (instance <= 0) + std::invalid_argument("instance id must be >= 1"); - // XXX: Only one metadata option must be set + // XXX: Only one metadata option must be set - AixLog::Log::init("snapclient", AixLog::Severity::trace, AixLog::Type::special); - if (debugOption->is_set()) - { - AixLog::Log::instance().add_logsink(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); - if (!debugOption->value().empty()) - AixLog::Log::instance().add_logsink(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(), "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); - } - else - { - AixLog::Log::instance().add_logsink(AixLog::Severity::info, AixLog::Type::all, "%Y-%m-%d %H-%M-%S [#severity]"); - } + AixLog::Log::init("snapclient", AixLog::Severity::trace, AixLog::Type::special); + if (debugOption->is_set()) + { + AixLog::Log::instance().add_logsink(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); + if (!debugOption->value().empty()) + AixLog::Log::instance().add_logsink(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(), + "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); + } + else + { + AixLog::Log::instance().add_logsink(AixLog::Severity::info, AixLog::Type::all, "%Y-%m-%d %H-%M-%S [#severity]"); + } - signal(SIGHUP, signal_handler); - signal(SIGTERM, signal_handler); - signal(SIGINT, signal_handler); + signal(SIGHUP, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); #ifdef HAS_DAEMON - std::unique_ptr daemon; - if (daemonOption->is_set()) - { - string pidFile = "/var/run/snapclient/pid"; - if (instance != 1) - pidFile += "." + cpt::to_string(instance); - string user = ""; - string group = ""; + std::unique_ptr daemon; + if (daemonOption->is_set()) + { + string pidFile = "/var/run/snapclient/pid"; + if (instance != 1) + pidFile += "." + cpt::to_string(instance); + string user = ""; + string group = ""; - if (userValue->is_set()) - { - if (userValue->value().empty()) - std::invalid_argument("user must not be empty"); + if (userValue->is_set()) + { + if (userValue->value().empty()) + std::invalid_argument("user must not be empty"); - vector user_group = utils::string::split(userValue->value(), ':'); - user = user_group[0]; - if (user_group.size() > 1) - group = user_group[1]; - } - daemon.reset(new Daemon(user, group, pidFile)); - daemon->daemonize(); - if (processPriority < -20) - processPriority = -20; - else if (processPriority > 19) - processPriority = 19; - if (processPriority != 0) - setpriority(PRIO_PROCESS, 0, processPriority); - SLOG(NOTICE) << "daemon started" << std::endl; - } + vector user_group = utils::string::split(userValue->value(), ':'); + user = user_group[0]; + if (user_group.size() > 1) + group = user_group[1]; + } + daemon.reset(new Daemon(user, group, pidFile)); + daemon->daemonize(); + if (processPriority < -20) + processPriority = -20; + else if (processPriority > 19) + processPriority = 19; + if (processPriority != 0) + setpriority(PRIO_PROCESS, 0, processPriority); + SLOG(NOTICE) << "daemon started" << std::endl; + } #endif - PcmDevice pcmDevice = getPcmDevice(soundcard); + PcmDevice pcmDevice = getPcmDevice(soundcard); #if defined(HAS_ALSA) - if (pcmDevice.idx == -1) - { - cout << "soundcard \"" << soundcard << "\" not found\n"; -// exit(EXIT_FAILURE); - } + if (pcmDevice.idx == -1) + { + cout << "soundcard \"" << soundcard << "\" not found\n"; + // exit(EXIT_FAILURE); + } #endif - if (host.empty()) - { + if (host.empty()) + { #if defined(HAS_AVAHI) || defined(HAS_BONJOUR) - BrowseZeroConf browser; - mDNSResult avahiResult; - while (!g_terminated) - { - try - { - if (browser.browse("_snapcast._tcp", avahiResult, 5000)) - { - host = avahiResult.ip; - port = avahiResult.port; - if (avahiResult.ip_version == IPVersion::IPv6) - host += "%" + cpt::to_string(avahiResult.iface_idx); - LOG(INFO) << "Found server " << host << ":" << port << "\n"; - break; - } - } - catch (const std::exception& e) - { - SLOG(ERROR) << "Exception: " << e.what() << std::endl; - } - chronos::sleep(500); - } + BrowseZeroConf browser; + mDNSResult avahiResult; + while (!g_terminated) + { + try + { + if (browser.browse("_snapcast._tcp", avahiResult, 5000)) + { + host = avahiResult.ip; + port = avahiResult.port; + if (avahiResult.ip_version == IPVersion::IPv6) + host += "%" + cpt::to_string(avahiResult.iface_idx); + LOG(INFO) << "Found server " << host << ":" << port << "\n"; + break; + } + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception: " << e.what() << std::endl; + } + chronos::sleep(500); + } #endif - } + } - // Setup metadata handling - std::shared_ptr meta; - meta.reset(new MetadataAdapter); - if(metaStderr) - meta.reset(new MetaStderrAdapter); + // Setup metadata handling + std::shared_ptr meta; + meta.reset(new MetadataAdapter); + if (metaStderr) + meta.reset(new MetaStderrAdapter); - std::unique_ptr controller(new Controller(hostIdValue->value(), instance, meta)); - if (!g_terminated) - { - LOG(INFO) << "Latency: " << latency << "\n"; - controller->start(pcmDevice, host, port, latency); - while(!g_terminated) - chronos::sleep(100); - controller->stop(); - } - } - catch (const std::exception& e) - { - SLOG(ERROR) << "Exception: " << e.what() << std::endl; - exitcode = EXIT_FAILURE; - } + std::unique_ptr controller(new Controller(hostIdValue->value(), instance, meta)); + if (!g_terminated) + { + LOG(INFO) << "Latency: " << latency << "\n"; + controller->start(pcmDevice, host, port, latency); + while (!g_terminated) + chronos::sleep(100); + controller->stop(); + } + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception: " << e.what() << std::endl; + exitcode = EXIT_FAILURE; + } - SLOG(NOTICE) << "daemon terminated." << endl; - exit(exitcode); + SLOG(NOTICE) << "daemon terminated." << endl; + exit(exitcode); } - - diff --git a/client/stream.cpp b/client/stream.cpp index 7c7e6039..3a836e20 100644 --- a/client/stream.cpp +++ b/client/stream.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -17,295 +17,301 @@ ***/ #include "stream.h" +#include "aixlog.hpp" +#include "timeProvider.h" #include #include #include -#include "aixlog.hpp" -#include "timeProvider.h" using namespace std; -//using namespace chronos; +// using namespace 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); - shortBuffer_.setSize(100); - miniBuffer_.setSize(20); -// cardBuffer_.setSize(50); + buffer_.setSize(500); + shortBuffer_.setSize(100); + miniBuffer_.setSize(20); + // cardBuffer_.setSize(50); -/* -48000 x -------- = ----- -47999,2 x - 1 + /* + 48000 x + ------- = ----- + 47999,2 x - 1 -x = 1,000016667 / (1,000016667 - 1) -*/ - setRealSampleRate(format_.rate); + x = 1,000016667 / (1,000016667 - 1) + */ + setRealSampleRate(format_.rate); } void Stream::setRealSampleRate(double sampleRate) { - if (sampleRate == format_.rate) - correctAfterXFrames_ = 0; - else - correctAfterXFrames_ = round((format_.rate / sampleRate) / (format_.rate / sampleRate - 1.)); -// LOG(DEBUG) << "Correct after X: " << correctAfterXFrames_ << " (Real rate: " << sampleRate << ", rate: " << format_.rate << ")\n"; + if (sampleRate == format_.rate) + correctAfterXFrames_ = 0; + else + correctAfterXFrames_ = round((format_.rate / sampleRate) / (format_.rate / sampleRate - 1.)); + // LOG(DEBUG) << "Correct after X: " << correctAfterXFrames_ << " (Real rate: " << sampleRate << ", rate: " << format_.rate << ")\n"; } void Stream::setBufferLen(size_t bufferLenMs) { - bufferMs_ = cs::msec(bufferLenMs); + bufferMs_ = cs::msec(bufferLenMs); } void Stream::clearChunks() { - while (chunks_.size() > 0) - chunks_.pop(); - resetBuffers(); + while (chunks_.size() > 0) + chunks_.pop(); + resetBuffers(); } void Stream::addChunk(msg::PcmChunk* chunk) { - while (chunks_.size() * chunk->duration().count() > 10000) - chunks_.pop(); - chunks_.push(shared_ptr(chunk)); -// LOG(DEBUG) << "new chunk: " << chunk->duration().count() << ", Chunks: " << chunks_.size() << "\n"; + while (chunks_.size() * chunk->duration().count() > 10000) + chunks_.pop(); + chunks_.push(shared_ptr(chunk)); + // LOG(DEBUG) << "new chunk: " << chunk->duration().count() << ", Chunks: " << chunks_.size() << "\n"; } bool Stream::waitForChunk(size_t ms) const { - return chunks_.wait_for(std::chrono::milliseconds(ms)); + return chunks_.wait_for(std::chrono::milliseconds(ms)); } cs::time_point_clk Stream::getSilentPlayerChunk(void* outputBuffer, unsigned long framesPerBuffer) { - if (!chunk_) - chunk_ = chunks_.pop(); - cs::time_point_clk tp = chunk_->start(); - memset(outputBuffer, 0, framesPerBuffer * format_.frameSize); - return tp; + if (!chunk_) + chunk_ = chunks_.pop(); + cs::time_point_clk tp = chunk_->start(); + memset(outputBuffer, 0, framesPerBuffer * format_.frameSize); + return tp; } /* time_point_ms Stream::seekTo(const time_point_ms& to) { - if (!chunk) - chunk_ = chunks_.pop(); + if (!chunk) + chunk_ = chunks_.pop(); // time_point_ms tp = chunk_->timePoint(); - while (to > chunk_->timePoint())// + std::chrono::milliseconds((long int)chunk_->getDuration()))// - { - chunk_ = chunks_.pop(); - LOG(DEBUG) << "\nto: " << Chunk::getAge(to) << "\t chunk: " << Chunk::getAge(chunk_->timePoint()) << "\n"; - LOG(DEBUG) << "diff: " << std::chrono::duration_cast((to - chunk_->timePoint())).count() << "\n"; - } - chunk_->seek(std::chrono::duration_cast(to - chunk_->timePoint()).count() * format_.msRate()); - return chunk_->timePoint(); + while (to > chunk_->timePoint())// + std::chrono::milliseconds((long int)chunk_->getDuration()))// + { + chunk_ = chunks_.pop(); + LOG(DEBUG) << "\nto: " << Chunk::getAge(to) << "\t chunk: " << Chunk::getAge(chunk_->timePoint()) << "\n"; + LOG(DEBUG) << "diff: " << std::chrono::duration_cast((to - chunk_->timePoint())).count() << "\n"; + } + chunk_->seek(std::chrono::duration_cast(to - chunk_->timePoint()).count() * format_.msRate()); + return chunk_->timePoint(); } */ /* time_point_clk Stream::seek(long ms) { - if (!chunk) - chunk_ = chunks_.pop(); + if (!chunk) + chunk_ = chunks_.pop(); - if (ms <= 0) - return chunk_->start(); + if (ms <= 0) + return chunk_->start(); // time_point_ms tp = chunk_->timePoint(); - while (ms > chunk_->duration().count()) - { - chunk_ = chunks_.pop(); - ms -= min(ms, (long)chunk_->durationLeft().count()); - } - chunk_->seek(ms * format_.msRate()); - return chunk_->start(); + while (ms > chunk_->duration().count()) + { + chunk_ = chunks_.pop(); + ms -= min(ms, (long)chunk_->durationLeft().count()); + } + chunk_->seek(ms * format_.msRate()); + return chunk_->start(); } */ cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec& timeout, unsigned long framesPerBuffer) { - if (!chunk_ && !chunks_.try_pop(chunk_, timeout)) - throw 0; + if (!chunk_ && !chunks_.try_pop(chunk_, timeout)) + throw 0; - cs::time_point_clk tp = chunk_->start(); - char* buffer = (char*)outputBuffer; - unsigned long read = 0; - while (read < framesPerBuffer) - { - read += chunk_->readFrames(buffer + read*format_.frameSize, framesPerBuffer - read); - if (chunk_->isEndOfChunk() && !chunks_.try_pop(chunk_, timeout)) - throw 0; - } - return tp; + cs::time_point_clk tp = chunk_->start(); + char* buffer = (char*)outputBuffer; + unsigned long read = 0; + while (read < framesPerBuffer) + { + read += chunk_->readFrames(buffer + read * format_.frameSize, framesPerBuffer - read); + if (chunk_->isEndOfChunk() && !chunks_.try_pop(chunk_, timeout)) + throw 0; + } + return tp; } cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec& timeout, unsigned long framesPerBuffer, long framesCorrection) { - if (framesCorrection == 0) - return getNextPlayerChunk(outputBuffer, timeout, framesPerBuffer); + if (framesCorrection == 0) + return getNextPlayerChunk(outputBuffer, timeout, framesPerBuffer); - long toRead = framesPerBuffer + framesCorrection; - char* buffer = (char*)malloc(toRead * format_.frameSize); - cs::time_point_clk tp = getNextPlayerChunk(buffer, timeout, toRead); + long toRead = framesPerBuffer + framesCorrection; + char* buffer = (char*)malloc(toRead * format_.frameSize); + cs::time_point_clk tp = getNextPlayerChunk(buffer, timeout, toRead); - float factor = (float)toRead / framesPerBuffer;//(float)(framesPerBuffer*channels_); -// if (abs(framesCorrection) > 1) -// LOG(INFO) << "correction: " << framesCorrection << ", factor: " << factor << "\n"; - float idx = 0; - for (size_t n=0; n 1) + // LOG(INFO) << "correction: " << framesCorrection << ", factor: " << factor << "\n"; + float idx = 0; + for (size_t n = 0; n < framesPerBuffer; ++n) + { + size_t index(floor(idx)); // = (int)(ceil(n*factor)); + memcpy((char*)outputBuffer + n * format_.frameSize, buffer + index * format_.frameSize, format_.frameSize); + idx += factor; + } + free(buffer); - return tp; + return tp; } void Stream::updateBuffers(int age) { - buffer_.add(age); - miniBuffer_.add(age); - shortBuffer_.add(age); + buffer_.add(age); + miniBuffer_.add(age); + shortBuffer_.add(age); } void Stream::resetBuffers() { - buffer_.clear(); - miniBuffer_.clear(); - shortBuffer_.clear(); + buffer_.clear(); + miniBuffer_.clear(); + shortBuffer_.clear(); } bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacTime, unsigned long framesPerBuffer) { - if (outputBufferDacTime > bufferMs_) - { - LOG(INFO) << "outputBufferDacTime > bufferMs: " << cs::duration(outputBufferDacTime) << " > " << cs::duration(bufferMs_) << "\n"; - sleep_ = cs::usec(0); - return false; - } + if (outputBufferDacTime > bufferMs_) + { + LOG(INFO) << "outputBufferDacTime > bufferMs: " << cs::duration(outputBufferDacTime) << " > " << cs::duration(bufferMs_) << "\n"; + sleep_ = cs::usec(0); + return false; + } - if (!chunk_ && !chunks_.try_pop(chunk_, outputBufferDacTime)) - { - //LOG(INFO) << "no chunks available\n"; - sleep_ = cs::usec(0); - return false; - } + if (!chunk_ && !chunks_.try_pop(chunk_, outputBufferDacTime)) + { + // LOG(INFO) << "no chunks available\n"; + sleep_ = cs::usec(0); + return false; + } - playedFrames_ += framesPerBuffer; + playedFrames_ += framesPerBuffer; - /// we have a chunk - /// age = chunk age (server now - rec time: some positive value) - buffer (e.g. 1000ms) + time to DAC - /// age = 0 => play now - /// age < 0 => play in -age - /// age > 0 => too old - cs::usec age = std::chrono::duration_cast(TimeProvider::serverNow() - chunk_->start()) - bufferMs_ + outputBufferDacTime; -// LOG(INFO) << "age: " << age.count() / 1000 << "\n"; - if ((sleep_.count() == 0) && (cs::abs(age) > cs::msec(200))) - { - LOG(INFO) << "age > 200: " << cs::duration(age) << "\n"; - sleep_ = age; - } + /// we have a chunk + /// age = chunk age (server now - rec time: some positive value) - buffer (e.g. 1000ms) + time to DAC + /// age = 0 => play now + /// age < 0 => play in -age + /// age > 0 => too old + cs::usec age = std::chrono::duration_cast(TimeProvider::serverNow() - chunk_->start()) - bufferMs_ + outputBufferDacTime; + // LOG(INFO) << "age: " << age.count() / 1000 << "\n"; + if ((sleep_.count() == 0) && (cs::abs(age) > cs::msec(200))) + { + LOG(INFO) << "age > 200: " << cs::duration(age) << "\n"; + sleep_ = age; + } - try - { + try + { - //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())); -// LOG(DEBUG) << "buffer duration: " << bufferDuration.count() << "\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())); + // LOG(DEBUG) << "buffer duration: " << bufferDuration.count() << "\n"; - cs::usec correction = cs::usec(0); - if (sleep_.count() != 0) - { - resetBuffers(); - if (sleep_ < -bufferDuration/2) - { - LOG(INFO) << "sleep < -bufferDuration/2: " << cs::duration(sleep_) << " < " << -cs::duration(bufferDuration)/2 << ", "; - // We're early: not enough chunks_. play silence. Reference chunk_ is the oldest (front) one - sleep_ = chrono::duration_cast(TimeProvider::serverNow() - getSilentPlayerChunk(outputBuffer, framesPerBuffer) - bufferMs_ + outputBufferDacTime); - LOG(INFO) << "sleep: " << cs::duration(sleep_) << "\n"; - if (sleep_ < -bufferDuration/2) - return true; - } - else if (sleep_ > bufferDuration/2) - { - LOG(INFO) << "sleep > bufferDuration/2: " << cs::duration(sleep_) << " > " << cs::duration(bufferDuration)/2 << "\n"; - // We're late: discard oldest chunks - while (sleep_ > chunk_->duration()) - { - LOG(INFO) << "sleep > chunkDuration: " << cs::duration(sleep_) << " > " << chunk_->duration().count() << ", chunks: " << chunks_.size() << ", out: " << cs::duration(outputBufferDacTime) << ", needed: " << cs::duration(bufferDuration) << "\n"; - sleep_ = std::chrono::duration_cast(TimeProvider::serverNow() - chunk_->start() - bufferMs_ + outputBufferDacTime); - if (!chunks_.try_pop(chunk_, outputBufferDacTime)) - { - LOG(INFO) << "no chunks available\n"; - chunk_ = NULL; - sleep_ = cs::usec(0); - return false; - } - } - } + cs::usec correction = cs::usec(0); + if (sleep_.count() != 0) + { + resetBuffers(); + if (sleep_ < -bufferDuration / 2) + { + LOG(INFO) << "sleep < -bufferDuration/2: " << cs::duration(sleep_) << " < " << -cs::duration(bufferDuration) / 2 << ", "; + // We're early: not enough chunks_. play silence. Reference chunk_ is the oldest (front) one + sleep_ = chrono::duration_cast(TimeProvider::serverNow() - getSilentPlayerChunk(outputBuffer, framesPerBuffer) - bufferMs_ + + outputBufferDacTime); + LOG(INFO) << "sleep: " << cs::duration(sleep_) << "\n"; + if (sleep_ < -bufferDuration / 2) + return true; + } + else if (sleep_ > bufferDuration / 2) + { + LOG(INFO) << "sleep > bufferDuration/2: " << cs::duration(sleep_) << " > " << cs::duration(bufferDuration) / 2 << "\n"; + // We're late: discard oldest chunks + while (sleep_ > chunk_->duration()) + { + LOG(INFO) << "sleep > chunkDuration: " << cs::duration(sleep_) << " > " << chunk_->duration().count() + << ", chunks: " << chunks_.size() << ", out: " << cs::duration(outputBufferDacTime) + << ", needed: " << cs::duration(bufferDuration) << "\n"; + sleep_ = std::chrono::duration_cast(TimeProvider::serverNow() - chunk_->start() - bufferMs_ + outputBufferDacTime); + if (!chunks_.try_pop(chunk_, outputBufferDacTime)) + { + LOG(INFO) << "no chunks available\n"; + chunk_ = NULL; + sleep_ = cs::usec(0); + return false; + } + } + } - // out of sync, can be corrected by playing faster/slower - if (sleep_ < -cs::usec(100)) - { - sleep_ += cs::usec(100); - correction = -cs::usec(100); - } - else if (sleep_ > cs::usec(100)) - { - sleep_ -= cs::usec(100); - correction = cs::usec(100); - } - else - { - LOG(INFO) << "Sleep " << cs::duration(sleep_) << "\n"; - correction = sleep_; - sleep_ = cs::usec(0); - } - } + // out of sync, can be corrected by playing faster/slower + if (sleep_ < -cs::usec(100)) + { + sleep_ += cs::usec(100); + correction = -cs::usec(100); + } + else if (sleep_ > cs::usec(100)) + { + sleep_ -= cs::usec(100); + correction = cs::usec(100); + } + else + { + LOG(INFO) << "Sleep " << cs::duration(sleep_) << "\n"; + correction = sleep_; + sleep_ = cs::usec(0); + } + } - // framesCorrection = number of frames to be read more or less to get in-sync - long framesCorrection = correction.count()*format_.usRate(); + // framesCorrection = number of frames to be read more or less to get in-sync + long framesCorrection = correction.count() * format_.usRate(); - // sample rate correction - if ((correctAfterXFrames_ != 0) && (playedFrames_ >= (unsigned long)abs(correctAfterXFrames_))) - { - framesCorrection += (correctAfterXFrames_ > 0)?1:-1; - playedFrames_ -= abs(correctAfterXFrames_); - } + // sample rate correction + if ((correctAfterXFrames_ != 0) && (playedFrames_ >= (unsigned long)abs(correctAfterXFrames_))) + { + framesCorrection += (correctAfterXFrames_ > 0) ? 1 : -1; + playedFrames_ -= abs(correctAfterXFrames_); + } - age = std::chrono::duration_cast(TimeProvider::serverNow() - getNextPlayerChunk(outputBuffer, outputBufferDacTime, framesPerBuffer, framesCorrection) - bufferMs_ + outputBufferDacTime); + age = std::chrono::duration_cast(TimeProvider::serverNow() - + getNextPlayerChunk(outputBuffer, outputBufferDacTime, framesPerBuffer, framesCorrection) - bufferMs_ + + outputBufferDacTime); - setRealSampleRate(format_.rate); - if (sleep_.count() == 0) - { - if (buffer_.full()) - { - if (cs::usec(abs(median_)) > cs::msec(1)) - { - LOG(INFO) << "pBuffer->full() && (abs(median_) > 1): " << median_ << "\n"; - sleep_ = cs::usec(median_); - } + setRealSampleRate(format_.rate); + if (sleep_.count() == 0) + { + if (buffer_.full()) + { + if (cs::usec(abs(median_)) > cs::msec(1)) + { + LOG(INFO) << "pBuffer->full() && (abs(median_) > 1): " << median_ << "\n"; + sleep_ = cs::usec(median_); + } /* else if (cs::usec(median_) > cs::usec(300)) { setRealSampleRate(format_.rate - format_.rate / 1000); @@ -315,63 +321,63 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT setRealSampleRate(format_.rate + format_.rate / 1000); } */ } - else if (shortBuffer_.full()) - { - if (cs::usec(abs(shortMedian_)) > cs::msec(5)) - { - LOG(INFO) << "pShortBuffer->full() && (abs(shortMedian_) > 5): " << shortMedian_ << "\n"; - sleep_ = cs::usec(shortMedian_); - } +else if (shortBuffer_.full()) +{ + if (cs::usec(abs(shortMedian_)) > cs::msec(5)) + { + LOG(INFO) << "pShortBuffer->full() && (abs(shortMedian_) > 5): " << shortMedian_ << "\n"; + sleep_ = cs::usec(shortMedian_); + } /* else { setRealSampleRate(format_.rate + -shortMedian_ / 100); } */ } - else if (miniBuffer_.full() && (cs::usec(abs(miniBuffer_.median())) > cs::msec(50))) - { - LOG(INFO) << "pMiniBuffer->full() && (abs(pMiniBuffer->mean()) > 50): " << miniBuffer_.median() << "\n"; - sleep_ = cs::usec((cs::msec::rep)miniBuffer_.mean()); - } - } - - if (sleep_.count() != 0) - { - static int lastAge(0); - int msAge = cs::duration(age); - if (lastAge != msAge) - { - lastAge = msAge; - LOG(INFO) << "Sleep " << cs::duration(sleep_) << ", age: " << msAge << ", bufferDuration: " << cs::duration(bufferDuration) << "\n"; - } - } - else if (shortBuffer_.full()) - { - if (cs::usec(shortMedian_) > cs::usec(100)) - setRealSampleRate(format_.rate * 0.9999); - else if (cs::usec(shortMedian_) < -cs::usec(100)) - setRealSampleRate(format_.rate * 1.0001); - } - - updateBuffers(age.count()); - - // print sync stats - time_t now = time(NULL); - if (now != lastUpdate_) - { - lastUpdate_ = now; - median_ = buffer_.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(outputBufferDacTime) << "\n"; -// LOG(INFO) << "Chunk: " << age.count()/1000 << "\t" << miniBuffer_.median()/1000 << "\t" << shortMedian_/1000 << "\t" << median_/1000 << "\t" << buffer_.size() << "\t" << cs::duration(outputBufferDacTime) << "\n"; - } - return (abs(cs::duration(age)) < 500); - } - catch(int e) - { - sleep_ = cs::usec(0); - return false; - } +else if (miniBuffer_.full() && (cs::usec(abs(miniBuffer_.median())) > cs::msec(50))) +{ + LOG(INFO) << "pMiniBuffer->full() && (abs(pMiniBuffer->mean()) > 50): " << miniBuffer_.median() << "\n"; + sleep_ = cs::usec((cs::msec::rep)miniBuffer_.mean()); } + } + if (sleep_.count() != 0) + { + static int lastAge(0); + int msAge = cs::duration(age); + if (lastAge != msAge) + { + lastAge = msAge; + LOG(INFO) << "Sleep " << cs::duration(sleep_) << ", age: " << msAge << ", bufferDuration: " << cs::duration(bufferDuration) + << "\n"; + } + } + else if (shortBuffer_.full()) + { + if (cs::usec(shortMedian_) > cs::usec(100)) + setRealSampleRate(format_.rate * 0.9999); + else if (cs::usec(shortMedian_) < -cs::usec(100)) + setRealSampleRate(format_.rate * 1.0001); + } + updateBuffers(age.count()); + // print sync stats + time_t now = time(NULL); + if (now != lastUpdate_) + { + lastUpdate_ = now; + median_ = buffer_.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(outputBufferDacTime) << "\n"; + // LOG(INFO) << "Chunk: " << age.count()/1000 << "\t" << miniBuffer_.median()/1000 << "\t" << shortMedian_/1000 << "\t" << median_/1000 << "\t" + //<< buffer_.size() << "\t" << cs::duration(outputBufferDacTime) << "\n"; + } + return (abs(cs::duration(age)) < 500); + } + catch (int e) + { + sleep_ = cs::usec(0); + return false; + } +} diff --git a/client/stream.h b/client/stream.h index 53f5c639..ff210bc9 100644 --- a/client/stream.h +++ b/client/stream.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -25,13 +25,13 @@ //#include //#include "common/timeUtils.h" -#include -#include +#include "common/queue.h" +#include "common/sampleFormat.h" #include "doubleBuffer.h" #include "message/message.h" #include "message/pcmChunk.h" -#include "common/sampleFormat.h" -#include "common/queue.h" +#include +#include /// Time synchronized audio stream @@ -42,57 +42,55 @@ class Stream { public: - Stream(const SampleFormat& format); + Stream(const SampleFormat& format); - /// Adds PCM data to the queue - void addChunk(msg::PcmChunk* chunk); - void clearChunks(); + /// Adds PCM data to the queue + void addChunk(msg::PcmChunk* chunk); + void clearChunks(); - /// Get PCM data, which will be played out in "outputBufferDacTime" time - /// frame = (num_channels) * (1 sample in bytes) = (2 channels) * (2 bytes (16 bits) per sample) = 4 bytes (32 bits) - bool getPlayerChunk(void* outputBuffer, const chronos::usec& outputBufferDacTime, unsigned long framesPerBuffer); + /// Get PCM data, which will be played out in "outputBufferDacTime" time + /// frame = (num_channels) * (1 sample in bytes) = (2 channels) * (2 bytes (16 bits) per sample) = 4 bytes (32 bits) + bool getPlayerChunk(void* outputBuffer, const chronos::usec& outputBufferDacTime, unsigned long framesPerBuffer); - /// "Server buffer": playout latency, e.g. 1000ms - void setBufferLen(size_t bufferLenMs); + /// "Server buffer": playout latency, e.g. 1000ms + void setBufferLen(size_t bufferLenMs); - const SampleFormat& getFormat() const - { - return format_; - } + const SampleFormat& getFormat() const + { + return format_; + } - bool waitForChunk(size_t ms) const; + bool waitForChunk(size_t ms) const; private: - chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long framesPerBuffer); - 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 seek(long ms); -// time_point_ms seekTo(const time_point_ms& to); - void updateBuffers(int age); - void resetBuffers(); - void setRealSampleRate(double sampleRate); + chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long framesPerBuffer); + 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 seek(long ms); + // time_point_ms seekTo(const time_point_ms& to); + void updateBuffers(int age); + void resetBuffers(); + void setRealSampleRate(double sampleRate); - SampleFormat format_; + SampleFormat format_; - chronos::usec sleep_; + chronos::usec sleep_; - Queue> chunks_; -// DoubleBuffer cardBuffer; - DoubleBuffer miniBuffer_; - DoubleBuffer buffer_; - DoubleBuffer shortBuffer_; - std::shared_ptr chunk_; + Queue> chunks_; + // DoubleBuffer cardBuffer; + DoubleBuffer miniBuffer_; + DoubleBuffer buffer_; + DoubleBuffer shortBuffer_; + std::shared_ptr chunk_; - int median_; - int shortMedian_; - time_t lastUpdate_; - unsigned long playedFrames_; - long correctAfterXFrames_; - chronos::msec bufferMs_; + int median_; + int shortMedian_; + time_t lastUpdate_; + unsigned long playedFrames_; + long correctAfterXFrames_; + chronos::msec bufferMs_; }; #endif - - diff --git a/client/timeProvider.cpp b/client/timeProvider.cpp index 66eede18..2ab9da87 100644 --- a/client/timeProvider.cpp +++ b/client/timeProvider.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -22,43 +22,42 @@ TimeProvider::TimeProvider() : diffToServer_(0) { - diffBuffer_.setSize(200); + diffBuffer_.setSize(200); } void TimeProvider::setDiff(const tv& c2s, const tv& s2c) { -// tv latency = c2s - s2c; -// 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.; - setDiffToServer(diff); + // tv latency = c2s - s2c; + // 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.; + setDiffToServer(diff); } void TimeProvider::setDiffToServer(double ms) { - static int32_t lastTimeSync = 0; - timeval now; - chronos::systemtimeofday(&now); + static int32_t lastTimeSync = 0; + timeval now; + chronos::systemtimeofday(&now); - /// clear diffBuffer if last update is older than a minute - if (!diffBuffer_.empty() && (std::abs(now.tv_sec - lastTimeSync) > 60)) - { - LOG(INFO) << "Last time sync older than a minute. Clearing time buffer\n"; - diffToServer_ = ms*1000; - diffBuffer_.clear(); - } - lastTimeSync = now.tv_sec; + /// clear diffBuffer if last update is older than a minute + if (!diffBuffer_.empty() && (std::abs(now.tv_sec - lastTimeSync) > 60)) + { + LOG(INFO) << "Last time sync older than a minute. Clearing time buffer\n"; + diffToServer_ = ms * 1000; + diffBuffer_.clear(); + } + lastTimeSync = now.tv_sec; - diffBuffer_.add(ms*1000); - diffToServer_ = diffBuffer_.median(3); -// LOG(INFO) << "setDiffToServer: " << ms << ", diff: " << diffToServer_ / 1000.f << "\n"; + diffBuffer_.add(ms * 1000); + diffToServer_ = diffBuffer_.median(3); + // LOG(INFO) << "setDiffToServer: " << ms << ", diff: " << diffToServer_ / 1000.f << "\n"; } /* long TimeProvider::getPercentileDiffToServer(size_t percentile) { - return diffBuffer.percentile(percentile); + return diffBuffer.percentile(percentile); } */ - diff --git a/client/timeProvider.h b/client/timeProvider.h index 14e2afe3..b71247cb 100644 --- a/client/timeProvider.h +++ b/client/timeProvider.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,11 +19,11 @@ #ifndef TIME_PROVIDER_H #define TIME_PROVIDER_H -#include -#include +#include "common/timeDefs.h" #include "doubleBuffer.h" #include "message/message.h" -#include "common/timeDefs.h" +#include +#include /// Provides local and server time @@ -35,57 +35,55 @@ class TimeProvider { public: - static TimeProvider& getInstance() - { - static TimeProvider instance; - return instance; - } + static TimeProvider& getInstance() + { + static TimeProvider instance; + return instance; + } - void setDiffToServer(double ms); - void setDiff(const tv& c2s, const tv& s2c); + void setDiffToServer(double ms); + void setDiff(const tv& c2s, const tv& s2c); - template - inline T getDiffToServer() const - { - return std::chrono::duration_cast(chronos::usec(diffToServer_)); - } + template + inline T getDiffToServer() const + { + return std::chrono::duration_cast(chronos::usec(diffToServer_)); + } -/* chronos::usec::rep getDiffToServer(); - chronos::usec::rep getPercentileDiffToServer(size_t percentile); - long getDiffToServerMs(); -*/ + /* chronos::usec::rep getDiffToServer(); + chronos::usec::rep getPercentileDiffToServer(size_t percentile); + long getDiffToServerMs(); + */ - template - static T sinceEpoche(const chronos::time_point_clk& point) - { - return std::chrono::duration_cast(point.time_since_epoch()); - } + template + static T sinceEpoche(const chronos::time_point_clk& point) + { + return std::chrono::duration_cast(point.time_since_epoch()); + } - static chronos::time_point_clk toTimePoint(const tv& timeval) - { - return chronos::time_point_clk(chronos::usec(timeval.usec) + chronos::sec(timeval.sec)); - } + static chronos::time_point_clk toTimePoint(const tv& timeval) + { + return chronos::time_point_clk(chronos::usec(timeval.usec) + chronos::sec(timeval.sec)); + } - inline static chronos::time_point_clk now() - { - return chronos::clk::now(); - } + inline static chronos::time_point_clk now() + { + return chronos::clk::now(); + } - inline static chronos::time_point_clk serverNow() - { - return chronos::clk::now() + TimeProvider::getInstance().getDiffToServer(); - } + inline static chronos::time_point_clk serverNow() + { + return chronos::clk::now() + TimeProvider::getInstance().getDiffToServer(); + } private: - TimeProvider(); - TimeProvider(TimeProvider const&); // Don't Implement - void operator=(TimeProvider const&); // Don't implement + TimeProvider(); + TimeProvider(TimeProvider const&); // Don't Implement + void operator=(TimeProvider const&); // Don't implement - DoubleBuffer diffBuffer_; - std::atomic diffToServer_; + DoubleBuffer diffBuffer_; + std::atomic diffToServer_; }; #endif - - diff --git a/cmake/CheckCXX11StringSupport.cmake b/cmake/CheckCXX11StringSupport.cmake index a0988081..d6ecb977 100644 --- a/cmake/CheckCXX11StringSupport.cmake +++ b/cmake/CheckCXX11StringSupport.cmake @@ -1,5 +1,5 @@ # 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 # it under the terms of the GNU General Public License as published by diff --git a/common/daemon.cpp b/common/daemon.cpp index 88f52f27..4ee01969 100644 --- a/common/daemon.cpp +++ b/common/daemon.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -18,154 +18,148 @@ #include "daemon.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include "common/snapException.h" #include "common/strCompat.h" -#include "common/utils/file_utils.h" #include "common/utils.h" +#include "common/utils/file_utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -Daemon::Daemon(const std::string& user, const std::string& group, const std::string& pidfile) : - pidFilehandle_(-1), - user_(user), - group_(group), - pidfile_(pidfile) +Daemon::Daemon(const std::string& user, const std::string& group, const std::string& pidfile) + : pidFilehandle_(-1), user_(user), group_(group), pidfile_(pidfile) { - if (pidfile.empty() || pidfile.find('/') == std::string::npos) - throw SnapException("invalid pid file \"" + pidfile + "\""); + if (pidfile.empty() || pidfile.find('/') == std::string::npos) + throw SnapException("invalid pid file \"" + pidfile + "\""); } Daemon::~Daemon() { - if (pidFilehandle_ != -1) - close(pidFilehandle_); + if (pidFilehandle_ != -1) + close(pidFilehandle_); } void Daemon::daemonize() { - std::string pidfileDir(pidfile_.substr(0, pidfile_.find_last_of('/'))); - utils::file::mkdirRecursive(pidfileDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + std::string pidfileDir(pidfile_.substr(0, pidfile_.find_last_of('/'))); + utils::file::mkdirRecursive(pidfileDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - /// Ensure only one copy - pidFilehandle_ = open(pidfile_.c_str(), O_RDWR|O_CREAT, 0644); - if (pidFilehandle_ == -1 ) - { - /// Couldn't open lock file - throw SnapException("Could not open PID lock file \"" + pidfile_ + "\""); - } + /// Ensure only one copy + pidFilehandle_ = open(pidfile_.c_str(), O_RDWR | O_CREAT, 0644); + if (pidFilehandle_ == -1) + { + /// Couldn't open lock file + throw SnapException("Could not open PID lock file \"" + pidfile_ + "\""); + } - uid_t user_uid = (uid_t)-1; - gid_t user_gid = (gid_t)-1; - std::string user_name; -//#ifdef FREEBSD -// bool had_group = false; -//#endif + uid_t user_uid = (uid_t)-1; + gid_t user_gid = (gid_t)-1; + std::string user_name; + //#ifdef FREEBSD + // bool had_group = false; + //#endif - if (!user_.empty()) - { - struct passwd *pwd = getpwnam(user_.c_str()); - if (pwd == nullptr) - throw SnapException("no such user \"" + user_ + "\""); - user_uid = pwd->pw_uid; - user_gid = pwd->pw_gid; - user_name = strdup(user_.c_str()); - /// this is needed by libs such as arts - setenv("HOME", pwd->pw_dir, true); - } + if (!user_.empty()) + { + struct passwd* pwd = getpwnam(user_.c_str()); + if (pwd == nullptr) + throw SnapException("no such user \"" + user_ + "\""); + user_uid = pwd->pw_uid; + user_gid = pwd->pw_gid; + user_name = strdup(user_.c_str()); + /// this is needed by libs such as arts + setenv("HOME", pwd->pw_dir, true); + } - if (!group_.empty()) - { - struct group *grp = getgrnam(group_.c_str()); - if (grp == nullptr) - throw SnapException("no such group \"" + group_ + "\""); - user_gid = grp->gr_gid; -//#ifdef FREEBSD -// had_group = true; -//#endif - } + if (!group_.empty()) + { + struct group* grp = getgrnam(group_.c_str()); + if (grp == nullptr) + throw SnapException("no such group \"" + group_ + "\""); + user_gid = grp->gr_gid; + //#ifdef FREEBSD + // had_group = true; + //#endif + } - if (chown(pidfile_.c_str(), user_uid, user_gid) == -1) - { - /// Couldn't open lock file - throw SnapException("Could not chown PID lock file \"" + pidfile_ + "\""); - } + if (chown(pidfile_.c_str(), user_uid, user_gid) == -1) + { + /// Couldn't open lock file + throw SnapException("Could not chown PID lock file \"" + pidfile_ + "\""); + } - /// set gid - 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)); + /// set gid + 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)); -//#if defined(FREEBSD) && !defined(MACOS) -//#ifdef FREEBSD - /// init supplementary groups - /// (must be done before we change our uid) - /// 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) -// throw SnapException("Failed to set supplementary groups of user \"" + user + "\""); -//#endif - /// set uid - if (user_uid != (uid_t)-1 && user_uid != getuid() && setuid(user_uid) == -1) - throw SnapException("Failed to set user " + user_); + //#if defined(FREEBSD) && !defined(MACOS) + //#ifdef FREEBSD + /// init supplementary groups + /// (must be done before we change our uid) + /// 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) + // throw SnapException("Failed to set supplementary groups of user \"" + user + "\""); + //#endif + /// set uid + if (user_uid != (uid_t)-1 && user_uid != getuid() && setuid(user_uid) == -1) + throw SnapException("Failed to set user " + user_); - /// Our process ID and Session ID - pid_t pid, sid; + /// Our process ID and Session ID + pid_t pid, sid; - /// Fork off the parent process - pid = fork(); - if (pid < 0) - exit(EXIT_FAILURE); + /// Fork off the parent process + pid = fork(); + if (pid < 0) + exit(EXIT_FAILURE); - /// If we got a good PID, then we can exit the parent process. - if (pid > 0) - exit(EXIT_SUCCESS); + /// If we got a good PID, then we can exit the parent process. + if (pid > 0) + exit(EXIT_SUCCESS); - /// Change the file mode mask - umask(0); + /// Change the file mode mask + umask(0); - /// Open any logs here + /// Open any logs here - /// Create a new SID for the child process - sid = setsid(); - if (sid < 0) - { - /// Log the failure - exit(EXIT_FAILURE); - } + /// Create a new SID for the child process + sid = setsid(); + if (sid < 0) + { + /// Log the failure + exit(EXIT_FAILURE); + } - /// Change the current working directory - if ((chdir("/")) < 0) - { - /// Log the failure - exit(EXIT_FAILURE); - } + /// Change the current working directory + if ((chdir("/")) < 0) + { + /// Log the failure + exit(EXIT_FAILURE); + } - /// Try to lock file - if (lockf(pidFilehandle_, F_TLOCK, 0) == -1) - throw SnapException("Could not lock PID lock file \"" + pidfile_ + "\""); + /// Try to lock file + if (lockf(pidFilehandle_, F_TLOCK, 0) == -1) + throw SnapException("Could not lock PID lock file \"" + pidfile_ + "\""); - char str[10]; - /// Get and format PID - sprintf(str, "%d\n", getpid()); + char str[10]; + /// Get and format PID + sprintf(str, "%d\n", getpid()); - /// write pid to lockfile - if (write(pidFilehandle_, str, strlen(str)) != (int)strlen(str)) - throw SnapException("Could not write PID to lock file \"" + pidfile_ + "\""); + /// write pid to lockfile + if (write(pidFilehandle_, str, strlen(str)) != (int)strlen(str)) + throw SnapException("Could not write PID to lock file \"" + pidfile_ + "\""); - /// Close out the standard file descriptors - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); + /// Close out the standard file descriptors + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); } - - - diff --git a/common/daemon.h b/common/daemon.h index 135ab012..6fb3ba5c 100644 --- a/common/daemon.h +++ b/common/daemon.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -25,17 +25,17 @@ class Daemon { public: - Daemon(const std::string& user, const std::string& group, const std::string& pidfile); - virtual ~Daemon(); + Daemon(const std::string& user, const std::string& group, const std::string& pidfile); + virtual ~Daemon(); + + void daemonize(); - void daemonize(); - private: - int pidFilehandle_; - std::string user_; - std::string group_; - std::string pidfile_; + int pidFilehandle_; + std::string user_; + std::string group_; + std::string pidfile_; }; -#endif // DAEMON_H +#endif // DAEMON_H diff --git a/common/message/codecHeader.h b/common/message/codecHeader.h index ff7167e2..8eb496c3 100644 --- a/common/message/codecHeader.h +++ b/common/message/codecHeader.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -30,42 +30,39 @@ namespace msg class CodecHeader : public BaseMessage { public: - CodecHeader(const std::string& codecName = "", size_t size = 0) : BaseMessage(message_type::kCodecHeader), payloadSize(size), codec(codecName) - { - payload = (char*)malloc(size); - } + CodecHeader(const std::string& codecName = "", size_t size = 0) : BaseMessage(message_type::kCodecHeader), payloadSize(size), codec(codecName) + { + payload = (char*)malloc(size); + } - virtual ~CodecHeader() - { - free(payload); - } + virtual ~CodecHeader() + { + free(payload); + } - virtual void read(std::istream& stream) - { - readVal(stream, codec); - readVal(stream, &payload, payloadSize); - } + virtual void read(std::istream& stream) + { + readVal(stream, codec); + readVal(stream, &payload, payloadSize); + } - virtual uint32_t getSize() const - { - return sizeof(uint32_t) + codec.size() + sizeof(uint32_t) + payloadSize; - } + virtual uint32_t getSize() const + { + return sizeof(uint32_t) + codec.size() + sizeof(uint32_t) + payloadSize; + } - uint32_t payloadSize; - char* payload; - std::string codec; + uint32_t payloadSize; + char* payload; + std::string codec; protected: - virtual void doserialize(std::ostream& stream) const - { - writeVal(stream, codec); - writeVal(stream, payload, payloadSize); - } + virtual void doserialize(std::ostream& stream) const + { + writeVal(stream, codec); + writeVal(stream, payload, payloadSize); + } }; - } #endif - - diff --git a/common/message/hello.h b/common/message/hello.h index e5cb0de3..f610ddfc 100644 --- a/common/message/hello.h +++ b/common/message/hello.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,9 +19,9 @@ #ifndef HELLO_MSG_H #define HELLO_MSG_H -#include "jsonMessage.h" -#include "common/utils.h" #include "common/strCompat.h" +#include "common/utils.h" +#include "jsonMessage.h" #include @@ -31,87 +31,84 @@ namespace msg class Hello : public JsonMessage { public: - Hello() : JsonMessage(message_type::kHello) - { - } + Hello() : JsonMessage(message_type::kHello) + { + } - Hello(const std::string& macAddress, const std::string& id, size_t instance) : JsonMessage(message_type::kHello) - { - msg["MAC"] = macAddress; - msg["HostName"] = ::getHostName(); - msg["Version"] = VERSION; - msg["ClientName"] = "Snapclient"; - msg["OS"] = ::getOS(); - msg["Arch"] = ::getArch(); - msg["Instance"] = instance; - msg["ID"] = id; - msg["SnapStreamProtocolVersion"] = 2; - } + Hello(const std::string& macAddress, const std::string& id, size_t instance) : JsonMessage(message_type::kHello) + { + msg["MAC"] = macAddress; + msg["HostName"] = ::getHostName(); + msg["Version"] = VERSION; + msg["ClientName"] = "Snapclient"; + msg["OS"] = ::getOS(); + msg["Arch"] = ::getArch(); + msg["Instance"] = instance; + msg["ID"] = id; + msg["SnapStreamProtocolVersion"] = 2; + } - virtual ~Hello() - { - } + virtual ~Hello() + { + } - std::string getMacAddress() const - { - return msg["MAC"]; - } + std::string getMacAddress() const + { + return msg["MAC"]; + } - std::string getHostName() const - { - return msg["HostName"]; - } + std::string getHostName() const + { + return msg["HostName"]; + } - std::string getVersion() const - { - return msg["Version"]; - } + std::string getVersion() const + { + return msg["Version"]; + } - std::string getClientName() const - { - return msg["ClientName"]; - } + std::string getClientName() const + { + return msg["ClientName"]; + } - std::string getOS() const - { - return msg["OS"]; - } + std::string getOS() const + { + return msg["OS"]; + } - std::string getArch() const - { - return msg["Arch"]; - } + std::string getArch() const + { + return msg["Arch"]; + } - int getInstance() const - { - return get("Instance", 1); - } + int getInstance() const + { + return get("Instance", 1); + } - int getProtocolVersion() const - { - return get("SnapStreamProtocolVersion", 1); - } + int getProtocolVersion() const + { + return get("SnapStreamProtocolVersion", 1); + } - std::string getId() const - { - return get("ID", getMacAddress()); - } + std::string getId() const + { + return get("ID", getMacAddress()); + } - std::string getUniqueId() const - { - std::string id = getId(); - int instance = getInstance(); - if (instance != 1) - { - id = id + "#" + cpt::to_string(instance); - } - return id; - } + std::string getUniqueId() const + { + std::string id = getId(); + int instance = getInstance(); + if (instance != 1) + { + id = id + "#" + cpt::to_string(instance); + } + return id; + } }; - } #endif - - diff --git a/common/message/jsonMessage.h b/common/message/jsonMessage.h index b005e3d6..1f01cd2c 100644 --- a/common/message/jsonMessage.h +++ b/common/message/jsonMessage.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ #ifndef JSON_MESSAGE_H #define JSON_MESSAGE_H -#include "message.h" #include "common/json.hpp" +#include "message.h" using json = nlohmann::json; @@ -32,54 +32,51 @@ namespace msg class JsonMessage : public BaseMessage { public: - JsonMessage(message_type msgType) : BaseMessage(msgType) - { - } + JsonMessage(message_type msgType) : BaseMessage(msgType) + { + } - virtual ~JsonMessage() - { - } + virtual ~JsonMessage() + { + } - virtual void read(std::istream& stream) - { - std::string s; - readVal(stream, s); - msg = json::parse(s); - } + virtual void read(std::istream& stream) + { + std::string s; + readVal(stream, s); + msg = json::parse(s); + } - virtual uint32_t getSize() const - { - return sizeof(uint32_t) + msg.dump().size(); - } + virtual uint32_t getSize() const + { + return sizeof(uint32_t) + msg.dump().size(); + } - json msg; + json msg; protected: - virtual void doserialize(std::ostream& stream) const - { - writeVal(stream, msg.dump()); - } + virtual void doserialize(std::ostream& stream) const + { + writeVal(stream, msg.dump()); + } - template - T get(const std::string& what, const T& def) const - { - try - { - if (!msg.count(what)) - return def; - return msg[what].get(); - } - catch(...) - { - return def; - } - } + template + T get(const std::string& what, const T& def) const + { + try + { + if (!msg.count(what)) + return def; + return msg[what].get(); + } + catch (...) + { + return def; + } + } }; - } #endif - - diff --git a/common/message/message.h b/common/message/message.h index fcf5ec55..8743279f 100644 --- a/common/message/message.h +++ b/common/message/message.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,92 +19,92 @@ #ifndef MESSAGE_H #define MESSAGE_H +#include "common/endian.hpp" +#include "common/timeDefs.h" #include #include #include #include -#include #include -#include "common/endian.hpp" -#include "common/timeDefs.h" +#include /* template > class vectorwrapbuf : public std::basic_streambuf { public: - vectorwrapbuf(std::vector &vec) - { - this->setg(vec.data(), vec.data(), vec.data() + vec.size()); - } + vectorwrapbuf(std::vector &vec) + { + this->setg(vec.data(), vec.data(), vec.data() + vec.size()); + } }; */ struct membuf : public std::basic_streambuf { - membuf(char* begin, char* end) - { - this->setg(begin, begin, end); - } + membuf(char* begin, char* end) + { + this->setg(begin, begin, end); + } }; enum message_type { - kBase = 0, - kCodecHeader = 1, - kWireChunk = 2, - kServerSettings = 3, - kTime = 4, - kHello = 5, - kStreamTags = 6, + kBase = 0, + kCodecHeader = 1, + kWireChunk = 2, + kServerSettings = 3, + kTime = 4, + kHello = 5, + kStreamTags = 6, - kFirst = kBase, - kLast = kStreamTags + kFirst = kBase, + kLast = kStreamTags }; struct tv { - tv() - { - timeval t; - chronos::systemtimeofday(&t); - sec = t.tv_sec; - usec = t.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() + { + timeval t; + chronos::systemtimeofday(&t); + sec = t.tv_sec; + usec = t.tv_usec; + } + tv(timeval tv) : sec(tv.tv_sec), usec(tv.tv_usec){}; + tv(int32_t _sec, int32_t _usec) : sec(_sec), usec(_usec){}; - int32_t sec; - int32_t usec; + int32_t sec; + int32_t usec; - tv operator+(const tv& other) const - { - tv result(*this); - result.sec += other.sec; - result.usec += other.usec; - if (result.usec > 1000000) - { - result.sec += result.usec / 1000000; - result.usec %= 1000000; - } - return result; - } + tv operator+(const tv& other) const + { + tv result(*this); + result.sec += other.sec; + result.usec += other.usec; + if (result.usec > 1000000) + { + result.sec += result.usec / 1000000; + result.usec %= 1000000; + } + return result; + } - tv operator-(const tv& other) const - { - tv result(*this); - result.sec -= other.sec; - result.usec -= other.usec; - while (result.usec < 0) - { - result.sec -= 1; - result.usec += 1000000; - } - return result; - } + tv operator-(const tv& other) const + { + tv result(*this); + result.sec -= other.sec; + result.usec -= other.usec; + while (result.usec < 0) + { + result.sec -= 1; + result.usec += 1000000; + } + return result; + } }; namespace msg @@ -118,197 +118,192 @@ using message_ptr = std::shared_ptr; struct BaseMessage { - BaseMessage() : type(kBase), id(0), refersTo(0) - { - } + BaseMessage() : type(kBase), id(0), refersTo(0) + { + } - BaseMessage(message_type type_) : type(type_), id(0), refersTo(0) - { - } + BaseMessage(message_type type_) : type(type_), id(0), refersTo(0) + { + } - virtual ~BaseMessage() - { - } + virtual ~BaseMessage() + { + } - virtual void read(std::istream& stream) - { - readVal(stream, type); - readVal(stream, id); - readVal(stream, refersTo); - readVal(stream, sent.sec); - readVal(stream, sent.usec); - readVal(stream, received.sec); - readVal(stream, received.usec); - readVal(stream, size); - } + virtual void read(std::istream& stream) + { + readVal(stream, type); + readVal(stream, id); + readVal(stream, refersTo); + readVal(stream, sent.sec); + readVal(stream, sent.usec); + readVal(stream, received.sec); + readVal(stream, received.usec); + readVal(stream, size); + } - void deserialize(char* payload) - { - membuf databuf(payload, payload + BaseMessage::getSize()); - std::istream is(&databuf); - read(is); - } + void deserialize(char* payload) + { + membuf databuf(payload, payload + BaseMessage::getSize()); + std::istream is(&databuf); + read(is); + } - void deserialize(const BaseMessage& baseMessage, char* payload) - { - type = baseMessage.type; - id = baseMessage.id; - refersTo = baseMessage.refersTo; - sent = baseMessage.sent; - received = baseMessage.received; - size = baseMessage.size; - membuf databuf(payload, payload + size); - std::istream is(&databuf); - read(is); - } + void deserialize(const BaseMessage& baseMessage, char* payload) + { + type = baseMessage.type; + id = baseMessage.id; + refersTo = baseMessage.refersTo; + sent = baseMessage.sent; + received = baseMessage.received; + size = baseMessage.size; + membuf databuf(payload, payload + size); + std::istream is(&databuf); + read(is); + } - virtual void serialize(std::ostream& stream) const - { - writeVal(stream, type); - writeVal(stream, id); - writeVal(stream, refersTo); - writeVal(stream, sent.sec); - writeVal(stream, sent.usec); - writeVal(stream, received.sec); - writeVal(stream, received.usec); - size = getSize(); - writeVal(stream, size); - doserialize(stream); - } + virtual void serialize(std::ostream& stream) const + { + writeVal(stream, type); + writeVal(stream, id); + writeVal(stream, refersTo); + writeVal(stream, sent.sec); + writeVal(stream, sent.usec); + writeVal(stream, received.sec); + writeVal(stream, received.usec); + size = getSize(); + writeVal(stream, size); + doserialize(stream); + } - virtual uint32_t getSize() const - { - return 3*sizeof(uint16_t) + 2*sizeof(tv) + sizeof(uint32_t); - }; + virtual uint32_t getSize() const + { + return 3 * sizeof(uint16_t) + 2 * sizeof(tv) + sizeof(uint32_t); + }; - uint16_t type; - mutable uint16_t id; - uint16_t refersTo; - tv received; - mutable tv sent; - mutable uint32_t size; + uint16_t type; + mutable uint16_t id; + uint16_t refersTo; + tv received; + mutable tv sent; + mutable uint32_t size; protected: - void writeVal(std::ostream& stream, const bool& val) const - { - char c = val?1:0; - writeVal(stream, c); - } + void writeVal(std::ostream& stream, const bool& val) const + { + char c = val ? 1 : 0; + writeVal(stream, c); + } - void writeVal(std::ostream& stream, const char& val) const - { - stream.write(reinterpret_cast(&val), sizeof(char)); - } + void writeVal(std::ostream& stream, const char& val) const + { + stream.write(reinterpret_cast(&val), sizeof(char)); + } - void writeVal(std::ostream& stream, const uint16_t& val) const - { - uint16_t v = SWAP_16(val); - stream.write(reinterpret_cast(&v), sizeof(uint16_t)); - } + void writeVal(std::ostream& stream, const uint16_t& val) const + { + uint16_t v = SWAP_16(val); + stream.write(reinterpret_cast(&v), sizeof(uint16_t)); + } - void writeVal(std::ostream& stream, const int16_t& val) const - { - uint16_t v = SWAP_16(val); - stream.write(reinterpret_cast(&v), sizeof(int16_t)); - } + void writeVal(std::ostream& stream, const int16_t& val) const + { + uint16_t v = SWAP_16(val); + stream.write(reinterpret_cast(&v), sizeof(int16_t)); + } - void writeVal(std::ostream& stream, const uint32_t& val) const - { - uint32_t v = SWAP_32(val); - stream.write(reinterpret_cast(&v), sizeof(uint32_t)); - } + void writeVal(std::ostream& stream, const uint32_t& val) const + { + uint32_t v = SWAP_32(val); + stream.write(reinterpret_cast(&v), sizeof(uint32_t)); + } - void writeVal(std::ostream& stream, const int32_t& val) const - { - uint32_t v = SWAP_32(val); - stream.write(reinterpret_cast(&v), sizeof(int32_t)); - } + void writeVal(std::ostream& stream, const int32_t& val) const + { + uint32_t v = SWAP_32(val); + stream.write(reinterpret_cast(&v), sizeof(int32_t)); + } - void writeVal(std::ostream& stream, const char* payload, const uint32_t& size) const - { - writeVal(stream, size); - stream.write(payload, size); - } + void writeVal(std::ostream& stream, const char* payload, const uint32_t& size) const + { + writeVal(stream, size); + stream.write(payload, size); + } - void writeVal(std::ostream& stream, const std::string& val) const - { - uint32_t size = val.size(); - writeVal(stream, val.c_str(), size); - } + void writeVal(std::ostream& stream, const std::string& val) const + { + uint32_t size = val.size(); + writeVal(stream, val.c_str(), size); + } - void readVal(std::istream& stream, bool& val) const - { - char c; - readVal(stream, c); - val = (c != 0); - } + void readVal(std::istream& stream, bool& val) const + { + char c; + readVal(stream, c); + val = (c != 0); + } - void readVal(std::istream& stream, char& val) const - { - stream.read(reinterpret_cast(&val), sizeof(char)); - } + void readVal(std::istream& stream, char& val) const + { + stream.read(reinterpret_cast(&val), sizeof(char)); + } - void readVal(std::istream& stream, uint16_t& val) const - { - stream.read(reinterpret_cast(&val), sizeof(uint16_t)); - val = SWAP_16(val); - } + void readVal(std::istream& stream, uint16_t& val) const + { + stream.read(reinterpret_cast(&val), sizeof(uint16_t)); + val = SWAP_16(val); + } - void readVal(std::istream& stream, int16_t& val) const - { - stream.read(reinterpret_cast(&val), sizeof(int16_t)); - val = SWAP_16(val); - } + void readVal(std::istream& stream, int16_t& val) const + { + stream.read(reinterpret_cast(&val), sizeof(int16_t)); + val = SWAP_16(val); + } - void readVal(std::istream& stream, uint32_t& val) const - { - stream.read(reinterpret_cast(&val), sizeof(uint32_t)); - val = SWAP_32(val); - } + void readVal(std::istream& stream, uint32_t& val) const + { + stream.read(reinterpret_cast(&val), sizeof(uint32_t)); + val = SWAP_32(val); + } - void readVal(std::istream& stream, int32_t& val) const - { - stream.read(reinterpret_cast(&val), sizeof(int32_t)); - val = SWAP_32(val); - } + void readVal(std::istream& stream, int32_t& val) const + { + stream.read(reinterpret_cast(&val), sizeof(int32_t)); + val = SWAP_32(val); + } - void readVal(std::istream& stream, char** payload, uint32_t& size) const - { - readVal(stream, size); - *payload = (char*)realloc(*payload, size); - stream.read(*payload, size); - } + void readVal(std::istream& stream, char** payload, uint32_t& size) const + { + readVal(stream, size); + *payload = (char*)realloc(*payload, size); + stream.read(*payload, size); + } - void readVal(std::istream& stream, std::string& val) const - { - uint32_t size; - readVal(stream, size); - val.resize(size); - stream.read(&val[0], size); - } + void readVal(std::istream& stream, std::string& val) const + { + uint32_t size; + readVal(stream, size); + val.resize(size); + stream.read(&val[0], size); + } - virtual void doserialize(std::ostream& stream) const - { - }; + virtual void doserialize(std::ostream& stream) const {}; }; struct SerializedMessage { - ~SerializedMessage() - { - free(buffer); - } + ~SerializedMessage() + { + free(buffer); + } - BaseMessage message; - char* buffer; + BaseMessage message; + char* buffer; }; - } #endif - - diff --git a/common/message/pcmChunk.h b/common/message/pcmChunk.h index 1e6c598d..68e56a68 100644 --- a/common/message/pcmChunk.h +++ b/common/message/pcmChunk.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,10 +19,10 @@ #ifndef PCM_CHUNK_H #define PCM_CHUNK_H -#include +#include "common/sampleFormat.h" #include "message.h" #include "wireChunk.h" -#include "common/sampleFormat.h" +#include namespace msg @@ -36,106 +36,95 @@ namespace msg class PcmChunk : public WireChunk { public: - PcmChunk(const SampleFormat& sampleFormat, size_t ms) : - WireChunk(sampleFormat.rate*sampleFormat.frameSize*ms / 1000), - format(sampleFormat), - idx_(0) - { - } + PcmChunk(const SampleFormat& sampleFormat, size_t ms) : WireChunk(sampleFormat.rate * sampleFormat.frameSize * ms / 1000), format(sampleFormat), idx_(0) + { + } - PcmChunk(const PcmChunk& pcmChunk) : - WireChunk(pcmChunk), - format(pcmChunk.format), - idx_(0) - { - } + PcmChunk(const PcmChunk& pcmChunk) : WireChunk(pcmChunk), format(pcmChunk.format), idx_(0) + { + } - PcmChunk() : WireChunk(), idx_(0) - { - } + PcmChunk() : WireChunk(), idx_(0) + { + } - virtual ~PcmChunk() - { - } + virtual ~PcmChunk() + { + } - int readFrames(void* outputBuffer, size_t frameCount) - { - //logd << "read: " << frameCount << ", total: " << (wireChunk->length / format.frameSize) << ", idx: " << idx;// << std::endl; - int result = frameCount; - if (idx_ + frameCount > (payloadSize / format.frameSize)) - result = (payloadSize / format.frameSize) - idx_; + int readFrames(void* outputBuffer, size_t frameCount) + { + // logd << "read: " << frameCount << ", total: " << (wireChunk->length / format.frameSize) << ", idx: " << idx;// << std::endl; + int result = frameCount; + if (idx_ + frameCount > (payloadSize / format.frameSize)) + result = (payloadSize / format.frameSize) - idx_; - //logd << ", from: " << format.frameSize*idx << ", to: " << format.frameSize*idx + format.frameSize*result; - if (outputBuffer != NULL) - memcpy((char*)outputBuffer, (char*)(payload) + format.frameSize*idx_, format.frameSize*result); + // logd << ", from: " << format.frameSize*idx << ", to: " << format.frameSize*idx + format.frameSize*result; + if (outputBuffer != NULL) + memcpy((char*)outputBuffer, (char*)(payload) + format.frameSize * idx_, format.frameSize * result); - idx_ += result; - //logd << ", new idx: " << idx << ", result: " << result << ", wireChunk->length: " << wireChunk->length << ", format.frameSize: " << format.frameSize << "\n";//std::endl; - return result; - } - - int seek(int frames) - { - if ((frames < 0) && (-frames > (int)idx_)) - frames = -idx_; - - idx_ += frames; - if (idx_ > getFrameCount()) - idx_ = getFrameCount(); + idx_ += result; + // logd << ", new idx: " << idx << ", result: " << result << ", wireChunk->length: " << wireChunk->length << ", format.frameSize: " << format.frameSize + // << "\n";//std::endl; + return result; + } - return idx_; - } + int seek(int frames) + { + if ((frames < 0) && (-frames > (int)idx_)) + frames = -idx_; + + idx_ += frames; + if (idx_ > getFrameCount()) + idx_ = getFrameCount(); + + return idx_; + } - virtual chronos::time_point_clk start() const - { - return chronos::time_point_clk( - chronos::sec(timestamp.sec) + - chronos::usec(timestamp.usec) + - chronos::usec((chronos::usec::rep)(1000000. * ((double)idx_ / (double)format.rate))) - ); - } + virtual chronos::time_point_clk start() const + { + return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec) + + chronos::usec((chronos::usec::rep)(1000000. * ((double)idx_ / (double)format.rate)))); + } - inline chronos::time_point_clk end() const - { - return start() + durationLeft(); - } + inline chronos::time_point_clk end() const + { + return start() + durationLeft(); + } - template - inline T duration() const - { - return std::chrono::duration_cast(chronos::nsec((chronos::nsec::rep)(1000000 * getFrameCount() / format.msRate()))); - } + template + inline T duration() const + { + return std::chrono::duration_cast(chronos::nsec((chronos::nsec::rep)(1000000 * getFrameCount() / format.msRate()))); + } - template - inline T durationLeft() const - { - return std::chrono::duration_cast(chronos::nsec((chronos::nsec::rep)(1000000 * (getFrameCount() - idx_) / format.msRate()))); - } + template + inline T durationLeft() const + { + return std::chrono::duration_cast(chronos::nsec((chronos::nsec::rep)(1000000 * (getFrameCount() - idx_) / format.msRate()))); + } - inline bool isEndOfChunk() const - { - return idx_ >= getFrameCount(); - } + inline bool isEndOfChunk() const + { + return idx_ >= getFrameCount(); + } - inline size_t getFrameCount() const - { - return (payloadSize / format.frameSize); - } + inline size_t getFrameCount() const + { + return (payloadSize / format.frameSize); + } - inline size_t getSampleCount() const - { - return (payloadSize / format.sampleSize); - } + inline size_t getSampleCount() const + { + return (payloadSize / format.sampleSize); + } - SampleFormat format; + SampleFormat format; private: - uint32_t idx_; + uint32_t idx_; }; - } #endif - - diff --git a/common/message/serverSettings.h b/common/message/serverSettings.h index ea6c6aaf..a23a391c 100644 --- a/common/message/serverSettings.h +++ b/common/message/serverSettings.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -28,64 +28,61 @@ namespace msg class ServerSettings : public JsonMessage { public: - ServerSettings() : JsonMessage(message_type::kServerSettings) - { - setBufferMs(0); - setLatency(0); - setVolume(100); - setMuted(false); - } + ServerSettings() : JsonMessage(message_type::kServerSettings) + { + setBufferMs(0); + setLatency(0); + setVolume(100); + setMuted(false); + } - virtual ~ServerSettings() - { - } + virtual ~ServerSettings() + { + } - int32_t getBufferMs() - { - return get("bufferMs", 0); - } + int32_t getBufferMs() + { + return get("bufferMs", 0); + } - int32_t getLatency() - { - return get("latency", 0); - } + int32_t getLatency() + { + return get("latency", 0); + } - uint16_t getVolume() - { - return get("volume", 100); - } + uint16_t getVolume() + { + return get("volume", 100); + } - bool isMuted() - { - return get("muted", false); - } + bool isMuted() + { + return get("muted", false); + } - void setBufferMs(int32_t bufferMs) - { - msg["bufferMs"] = bufferMs; - } + void setBufferMs(int32_t bufferMs) + { + msg["bufferMs"] = bufferMs; + } - void setLatency(int32_t latency) - { - msg["latency"] = latency; - } + void setLatency(int32_t latency) + { + msg["latency"] = latency; + } - void setVolume(uint16_t volume) - { - msg["volume"] = volume; - } + void setVolume(uint16_t volume) + { + msg["volume"] = volume; + } - void setMuted(bool muted) - { - msg["muted"] = muted; - } + void setMuted(bool muted) + { + msg["muted"] = muted; + } }; - } #endif - - diff --git a/common/message/streamTags.h b/common/message/streamTags.h index 0b8024c9..c3eee316 100644 --- a/common/message/streamTags.h +++ b/common/message/streamTags.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -24,12 +24,12 @@ /* * Due to the PCM pipe implementation of snapcast input we cannot know track start/end * it's all a long stream (although we detect idle situations) - * + * * So, we cannot push metadata on start of track as we don't know when that is. - * + * * I.E. we push metadata as we get an update, as we don't know when an update * is complete (different meta supported in different stream interfaces) - * it is the streamreaders responsibility to update metadata and + * it is the streamreaders responsibility to update metadata and * trigger a client notification. * * I.E. we need to suppply the client notification mechanism. @@ -41,38 +41,35 @@ namespace msg class StreamTags : public JsonMessage { public: - /* - Usage: - json jtag = { - {"artist", "Pink Floyd"}, - {"album", "Dark Side of the Moon"}, - {"track", "Money"}, - {"spotifyid", "akjhasi7wehke7698"}, - {"musicbrainzid", "akjhasi7wehke7698"}, - }; - this->meta_.reset(new msg::StreamTags(jtag)); + /* + Usage: + json jtag = { + {"artist", "Pink Floyd"}, + {"album", "Dark Side of the Moon"}, + {"track", "Money"}, + {"spotifyid", "akjhasi7wehke7698"}, + {"musicbrainzid", "akjhasi7wehke7698"}, + }; + this->meta_.reset(new msg::StreamTags(jtag)); - Stream input can decide on tags, IDK... but smart - to use some common naming scheme - */ + Stream input can decide on tags, IDK... but smart + to use some common naming scheme + */ - StreamTags(json j) : JsonMessage(message_type::kStreamTags) - { - msg = j; - } + StreamTags(json j) : JsonMessage(message_type::kStreamTags) + { + msg = j; + } - StreamTags() : JsonMessage(message_type::kStreamTags) - { - } + StreamTags() : JsonMessage(message_type::kStreamTags) + { + } - virtual ~StreamTags() - { - } + virtual ~StreamTags() + { + } }; - } #endif - - diff --git a/common/message/time.h b/common/message/time.h index 7e3ef8e5..43391aeb 100644 --- a/common/message/time.h +++ b/common/message/time.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -27,38 +27,35 @@ namespace msg class Time : public BaseMessage { public: - Time() : BaseMessage(message_type::kTime) - { - } + Time() : BaseMessage(message_type::kTime) + { + } - virtual ~Time() - { - } + virtual ~Time() + { + } - virtual void read(std::istream& stream) - { - readVal(stream, latency.sec); - readVal(stream, latency.usec); - } + virtual void read(std::istream& stream) + { + readVal(stream, latency.sec); + readVal(stream, latency.usec); + } - virtual uint32_t getSize() const - { - return sizeof(tv); - } + virtual uint32_t getSize() const + { + return sizeof(tv); + } - tv latency; + tv latency; protected: - virtual void doserialize(std::ostream& stream) const - { - writeVal(stream, latency.sec); - writeVal(stream, latency.usec); - } + virtual void doserialize(std::ostream& stream) const + { + writeVal(stream, latency.sec); + writeVal(stream, latency.usec); + } }; - } #endif - - diff --git a/common/message/wireChunk.h b/common/message/wireChunk.h index 9d8682ca..f8c65e71 100644 --- a/common/message/wireChunk.h +++ b/common/message/wireChunk.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,14 +19,14 @@ #ifndef WIRE_CHUNK_H #define WIRE_CHUNK_H +#include "common/timeDefs.h" +#include "message.h" #include #include #include #include #include #include -#include "message.h" -#include "common/timeDefs.h" namespace msg @@ -39,55 +39,52 @@ namespace msg class WireChunk : public BaseMessage { public: - WireChunk(size_t size = 0) : BaseMessage(message_type::kWireChunk), payloadSize(size) - { - payload = (char*)malloc(size); - } + WireChunk(size_t size = 0) : BaseMessage(message_type::kWireChunk), payloadSize(size) + { + payload = (char*)malloc(size); + } - WireChunk(const WireChunk& wireChunk) : BaseMessage(message_type::kWireChunk), timestamp(wireChunk.timestamp), payloadSize(wireChunk.payloadSize) - { - payload = (char*)malloc(payloadSize); - memcpy(payload, wireChunk.payload, payloadSize); - } + WireChunk(const WireChunk& wireChunk) : BaseMessage(message_type::kWireChunk), timestamp(wireChunk.timestamp), payloadSize(wireChunk.payloadSize) + { + payload = (char*)malloc(payloadSize); + memcpy(payload, wireChunk.payload, payloadSize); + } - virtual ~WireChunk() - { - free(payload); - } + virtual ~WireChunk() + { + free(payload); + } - virtual void read(std::istream& stream) - { - readVal(stream, timestamp.sec); - readVal(stream, timestamp.usec); - readVal(stream, &payload, payloadSize); - } + virtual void read(std::istream& stream) + { + readVal(stream, timestamp.sec); + readVal(stream, timestamp.usec); + readVal(stream, &payload, payloadSize); + } - virtual uint32_t getSize() const - { - return sizeof(tv) + sizeof(int32_t) + payloadSize; - } + virtual uint32_t getSize() const + { + return sizeof(tv) + sizeof(int32_t) + payloadSize; + } - virtual chronos::time_point_clk start() const - { - return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec)); - } + virtual chronos::time_point_clk start() const + { + return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec)); + } - tv timestamp; - uint32_t payloadSize; - char* payload; + tv timestamp; + uint32_t payloadSize; + char* payload; protected: - virtual void doserialize(std::ostream& stream) const - { - writeVal(stream, timestamp.sec); - writeVal(stream, timestamp.usec); - writeVal(stream, payload, payloadSize); - } + virtual void doserialize(std::ostream& stream) const + { + writeVal(stream, timestamp.sec); + writeVal(stream, timestamp.usec); + writeVal(stream, payload, payloadSize); + } }; - } #endif - - diff --git a/common/queue.h b/common/queue.h index 765dfe29..8f3be560 100644 --- a/common/queue.h +++ b/common/queue.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,147 +19,144 @@ #ifndef QUEUE_H #define QUEUE_H -#include #include -#include -#include #include +#include +#include +#include template class Queue { public: + T pop() + { + std::unique_lock mlock(mutex_); + while (queue_.empty()) + cond_.wait(mlock); - T pop() - { - std::unique_lock mlock(mutex_); - while (queue_.empty()) - cond_.wait(mlock); + // std::lock_guard lock(mutex_); + auto val = queue_.front(); + queue_.pop_front(); + return val; + } -// std::lock_guard lock(mutex_); - auto val = queue_.front(); - queue_.pop_front(); - return val; - } + T front() + { + std::unique_lock mlock(mutex_); + while (queue_.empty()) + cond_.wait(mlock); - T front() - { - std::unique_lock mlock(mutex_); - while (queue_.empty()) - cond_.wait(mlock); + return queue_.front(); + } - return queue_.front(); - } + void abort_wait() + { + { + std::lock_guard mlock(mutex_); + abort_ = true; + } + cond_.notify_one(); + } - void abort_wait() - { - { - std::lock_guard mlock(mutex_); - abort_ = true; - } - cond_.notify_one(); - } + bool wait_for(std::chrono::milliseconds timeout) const + { + std::unique_lock mlock(mutex_); + abort_ = false; + if (!cond_.wait_for(mlock, timeout, [this] { return (!queue_.empty() || abort_); })) + return false; - bool wait_for(std::chrono::milliseconds timeout) const - { - std::unique_lock mlock(mutex_); - abort_ = false; - if (!cond_.wait_for(mlock, timeout, [this] { return (!queue_.empty() || abort_); })) - return false; - - return !queue_.empty() && !abort_; - } + return !queue_.empty() && !abort_; + } - bool try_pop(T& item, std::chrono::microseconds timeout) - { - std::unique_lock mlock(mutex_); - abort_ = false; - if (!cond_.wait_for(mlock, timeout, [this] { return (!queue_.empty() || abort_); })) - return false; - - if (queue_.empty() || abort_) - return false; + bool try_pop(T& item, std::chrono::microseconds timeout) + { + std::unique_lock mlock(mutex_); + abort_ = false; + if (!cond_.wait_for(mlock, timeout, [this] { return (!queue_.empty() || abort_); })) + return false; - item = std::move(queue_.front()); - queue_.pop_front(); + if (queue_.empty() || abort_) + return false; - return true; - } + item = std::move(queue_.front()); + queue_.pop_front(); - bool try_pop(T& item, std::chrono::milliseconds timeout) - { - return try_pop(item, std::chrono::duration_cast(timeout)); - } + return true; + } - void pop(T& item) - { - std::unique_lock mlock(mutex_); - while (queue_.empty()) - cond_.wait(mlock); + bool try_pop(T& item, std::chrono::milliseconds timeout) + { + return try_pop(item, std::chrono::duration_cast(timeout)); + } - item = queue_.front(); - queue_.pop_front(); - } + void pop(T& item) + { + std::unique_lock mlock(mutex_); + while (queue_.empty()) + cond_.wait(mlock); - void push_front(const T& item) - { - { - std::lock_guard mlock(mutex_); - queue_.push_front(item); - } - cond_.notify_one(); - } + item = queue_.front(); + queue_.pop_front(); + } - void push_front(T&& item) - { - { - std::lock_guard mlock(mutex_); - queue_.push_front(std::move(item)); - } - cond_.notify_one(); - } + void push_front(const T& item) + { + { + std::lock_guard mlock(mutex_); + queue_.push_front(item); + } + cond_.notify_one(); + } - void push(const T& item) - { - { - std::lock_guard mlock(mutex_); - queue_.push_back(item); - } - cond_.notify_one(); - } + void push_front(T&& item) + { + { + std::lock_guard mlock(mutex_); + queue_.push_front(std::move(item)); + } + cond_.notify_one(); + } - void push(T&& item) - { - { - std::lock_guard mlock(mutex_); - queue_.push_back(std::move(item)); - } - cond_.notify_one(); - } + void push(const T& item) + { + { + std::lock_guard mlock(mutex_); + queue_.push_back(item); + } + cond_.notify_one(); + } - size_t size() const - { - std::lock_guard mlock(mutex_); - return queue_.size(); - } + void push(T&& item) + { + { + std::lock_guard mlock(mutex_); + queue_.push_back(std::move(item)); + } + cond_.notify_one(); + } - bool empty() const - { - return (size() == 0); - } + size_t size() const + { + std::lock_guard mlock(mutex_); + return queue_.size(); + } - Queue()=default; - Queue(const Queue&) = delete; // disable copying - Queue& operator=(const Queue&) = delete; // disable assignment + bool empty() const + { + return (size() == 0); + } + + Queue() = default; + Queue(const Queue&) = delete; // disable copying + Queue& operator=(const Queue&) = delete; // disable assignment private: - std::deque queue_; - mutable std::atomic abort_; - mutable std::mutex mutex_; - mutable std::condition_variable cond_; + std::deque queue_; + mutable std::atomic abort_; + mutable std::mutex mutex_; + mutable std::condition_variable cond_; }; #endif - - diff --git a/common/sampleFormat.cpp b/common/sampleFormat.cpp index b91d28ff..2de06cd7 100644 --- a/common/sampleFormat.cpp +++ b/common/sampleFormat.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,15 +16,15 @@ along with this program. If not, see . ***/ -#include #include #include +#include -#include "sampleFormat.h" -#include "common/strCompat.h" -#include "common/utils/string_utils.h" -#include "common/utils.h" #include "aixlog.hpp" +#include "common/strCompat.h" +#include "common/utils.h" +#include "common/utils/string_utils.h" +#include "sampleFormat.h" using namespace std; @@ -37,49 +37,44 @@ SampleFormat::SampleFormat() SampleFormat::SampleFormat(const std::string& format) { - setFormat(format); + setFormat(format); } SampleFormat::SampleFormat(uint32_t sampleRate, uint16_t bitsPerSample, uint16_t channelCount) { - setFormat(sampleRate, bitsPerSample, channelCount); + setFormat(sampleRate, bitsPerSample, channelCount); } string SampleFormat::getFormat() const { - stringstream ss; - ss << rate << ":" << bits << ":" << channels; - return ss.str(); + stringstream ss; + ss << rate << ":" << bits << ":" << channels; + return ss.str(); } void SampleFormat::setFormat(const std::string& format) { - std::vector strs; - strs = utils::string::split(format, ':'); - if (strs.size() == 3) - setFormat( - cpt::stoul(strs[0]), - cpt::stoul(strs[1]), - cpt::stoul(strs[2])); + std::vector strs; + strs = utils::string::split(format, ':'); + if (strs.size() == 3) + setFormat(cpt::stoul(strs[0]), cpt::stoul(strs[1]), cpt::stoul(strs[2])); } void SampleFormat::setFormat(uint32_t rate, uint16_t bits, uint16_t channels) { - //needs something like: - // 24_4 = 3 bytes, padded to 4 - // 32 = 4 bytes - this->rate = rate; - this->bits = bits; - this->channels = channels; - sampleSize = bits / 8; - if (bits == 24) - sampleSize = 4; - frameSize = channels*sampleSize; -// LOG(DEBUG) << "SampleFormat: " << rate << ":" << bits << ":" << channels << "\n"; + // needs something like: + // 24_4 = 3 bytes, padded to 4 + // 32 = 4 bytes + this->rate = rate; + this->bits = bits; + this->channels = channels; + sampleSize = bits / 8; + if (bits == 24) + sampleSize = 4; + frameSize = channels * sampleSize; + // LOG(DEBUG) << "SampleFormat: " << rate << ":" << bits << ":" << channels << "\n"; } - - diff --git a/common/sampleFormat.h b/common/sampleFormat.h index 4a928422..4a66cad7 100644 --- a/common/sampleFormat.h +++ b/common/sampleFormat.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -31,46 +31,46 @@ * 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) * 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 { public: - SampleFormat(); - SampleFormat(const std::string& format); - SampleFormat(uint32_t rate, uint16_t bits, uint16_t channels); + SampleFormat(); + SampleFormat(const std::string& format); + SampleFormat(uint32_t rate, uint16_t bits, uint16_t channels); - std::string getFormat() const; + std::string getFormat() const; - void setFormat(const std::string& format); - void setFormat(uint32_t rate, uint16_t bits, uint16_t channels); + void setFormat(const std::string& format); + void setFormat(uint32_t rate, uint16_t bits, uint16_t channels); - uint32_t rate; - uint16_t bits; - uint16_t channels; + uint32_t rate; + uint16_t bits; + uint16_t channels; - // size in [bytes] of a single mono sample, e.g. 2 bytes (= 16 bits) - uint16_t sampleSize; + // size in [bytes] of a single mono sample, e.g. 2 bytes (= 16 bits) + uint16_t sampleSize; - // size in [bytes] of a frame (sum of sample sizes = #channel*sampleSize), e.g. 4 bytes (= 2 channel * 16 bit) - uint16_t frameSize; + // size in [bytes] of a frame (sum of sample sizes = #channel*sampleSize), e.g. 4 bytes (= 2 channel * 16 bit) + uint16_t frameSize; - inline double msRate() const - { - return (double)rate/1000.; - } + inline double msRate() const + { + return (double)rate / 1000.; + } - inline double usRate() const - { - return (double)rate/1000000.; - } + inline double usRate() const + { + return (double)rate / 1000000.; + } - inline double nsRate() const - { - return (double)rate/1000000000.; - } + inline double nsRate() const + { + return (double)rate / 1000000000.; + } }; #endif - diff --git a/common/signalHandler.h b/common/signalHandler.h index 88abb243..81031c8c 100644 --- a/common/signalHandler.h +++ b/common/signalHandler.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -27,25 +27,23 @@ extern volatile sig_atomic_t g_terminated; void signal_handler(int sig) { - switch(sig) - { - case SIGHUP: - syslog(LOG_WARNING, "Received SIGHUP signal."); - break; - case SIGTERM: - syslog(LOG_WARNING, "Received SIGTERM signal."); - g_terminated = true; - break; - case SIGINT: - syslog(LOG_WARNING, "Received SIGINT signal."); - g_terminated = true; - break; - default: - syslog(LOG_WARNING, "Unhandled signal "); - break; - } + switch (sig) + { + case SIGHUP: + syslog(LOG_WARNING, "Received SIGHUP signal."); + break; + case SIGTERM: + syslog(LOG_WARNING, "Received SIGTERM signal."); + g_terminated = true; + break; + case SIGINT: + syslog(LOG_WARNING, "Received SIGINT signal."); + g_terminated = true; + break; + default: + syslog(LOG_WARNING, "Unhandled signal "); + break; + } } #endif - - diff --git a/common/snapException.h b/common/snapException.h index 12b2aed6..2ca86487 100644 --- a/common/snapException.h +++ b/common/snapException.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,37 +19,39 @@ #ifndef SNAP_EXCEPTION_H #define SNAP_EXCEPTION_H +#include // std::strlen, std::strcpy #include #include -#include // std::strlen, std::strcpy // text_exception uses a dynamically-allocated internal c-string for what(): -class SnapException : public std::exception { - char* text_; +class SnapException : public std::exception +{ + char* text_; + public: - SnapException(const char* text) - { - text_ = new char[std::strlen(text) + 1]; - std::strcpy(text_, text); - } + SnapException(const char* text) + { + text_ = new char[std::strlen(text) + 1]; + std::strcpy(text_, text); + } - SnapException(const std::string& text) : SnapException(text.c_str()) - { - } + SnapException(const std::string& text) : SnapException(text.c_str()) + { + } - SnapException(const SnapException& e) : SnapException(e.what()) - { - } + SnapException(const SnapException& e) : SnapException(e.what()) + { + } - virtual ~SnapException() throw() - { - delete[] text_; - } + virtual ~SnapException() throw() + { + delete[] text_; + } - virtual const char* what() const noexcept - { - return text_; - } + virtual const char* what() const noexcept + { + return text_; + } }; @@ -57,26 +59,24 @@ public: class AsyncSnapException : public SnapException { public: - AsyncSnapException(const char* text) : SnapException(text) - { - } + AsyncSnapException(const char* text) : SnapException(text) + { + } - AsyncSnapException(const std::string& text) : SnapException(text) - { - } + AsyncSnapException(const std::string& text) : SnapException(text) + { + } - AsyncSnapException(const AsyncSnapException& e) : SnapException(e.what()) - { - } + AsyncSnapException(const AsyncSnapException& e) : SnapException(e.what()) + { + } - virtual ~AsyncSnapException() throw() - { - } + virtual ~AsyncSnapException() throw() + { + } }; #endif - - diff --git a/common/strCompat.h b/common/strCompat.h index 31ebce7c..0d71a3d4 100644 --- a/common/strCompat.h +++ b/common/strCompat.h @@ -2,105 +2,104 @@ #define COMPAT_H -#include #include +#include #ifdef NO_CPP11_STRING -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #endif namespace cpt { - static struct lconv* localeconv() - { - #ifdef NO_CPP11_STRING - static struct lconv result; - result.decimal_point = nullptr; - result.thousands_sep = nullptr; - return &result; - #else - return std::localeconv(); - #endif - } +static struct lconv* localeconv() +{ +#ifdef NO_CPP11_STRING + static struct lconv result; + result.decimal_point = nullptr; + result.thousands_sep = nullptr; + return &result; +#else + return std::localeconv(); +#endif +} - template - static std::string to_string(const T& t) - { - #ifdef NO_CPP11_STRING - std::stringstream ss; - ss << t; - return ss.str(); - #else - return std::to_string(t); - #endif - } +template +static std::string to_string(const T& t) +{ +#ifdef NO_CPP11_STRING + std::stringstream ss; + ss << t; + return ss.str(); +#else + return std::to_string(t); +#endif +} - static long stoul(const std::string& str) - { - #ifdef NO_CPP11_STRING - errno = 0; - char *temp; - long val = strtol(str.c_str(), &temp, 10); - if (temp == str.c_str() || *temp != '\0') - throw std::invalid_argument("stoi"); - if (((val == LONG_MIN) || (val == LONG_MAX)) && (errno == ERANGE)) - throw std::out_of_range("stoi"); - return val; - #else - return std::stoul(str); - #endif - } +static long stoul(const std::string& str) +{ +#ifdef NO_CPP11_STRING + errno = 0; + char* temp; + long val = strtol(str.c_str(), &temp, 10); + if (temp == str.c_str() || *temp != '\0') + throw std::invalid_argument("stoi"); + if (((val == LONG_MIN) || (val == LONG_MAX)) && (errno == ERANGE)) + throw std::out_of_range("stoi"); + return val; +#else + return std::stoul(str); +#endif +} - static int stoi(const std::string& str) - { - #ifdef NO_CPP11_STRING - return cpt::stoul(str); - #else - return std::stoi(str); - #endif - } +static int stoi(const std::string& str) +{ +#ifdef NO_CPP11_STRING + return cpt::stoul(str); +#else + return std::stoi(str); +#endif +} - static double stod(const std::string& str) - { - #ifdef NO_CPP11_STRING - errno = 0; - char *temp; - double val = strtod(str.c_str(), &temp); - if (temp == str.c_str() || *temp != '\0') - throw std::invalid_argument("stod"); - if ((val == HUGE_VAL) && (errno == ERANGE)) - throw std::out_of_range("stod"); - return val; - #else - return std::stod(str.c_str()); - #endif - } +static double stod(const std::string& str) +{ +#ifdef NO_CPP11_STRING + errno = 0; + char* temp; + double val = strtod(str.c_str(), &temp); + if (temp == str.c_str() || *temp != '\0') + throw std::invalid_argument("stod"); + if ((val == HUGE_VAL) && (errno == ERANGE)) + throw std::out_of_range("stod"); + return val; +#else + return std::stod(str.c_str()); +#endif +} - static long double strtold(const char* str, char** endptr) - { - #ifdef NO_CPP11_STRING - return cpt::stod(str); - #else - return std::strtold(str, endptr); - #endif - } +static long double strtold(const char* str, char** endptr) +{ +#ifdef NO_CPP11_STRING + return cpt::stod(str); +#else + return std::strtold(str, endptr); +#endif +} - static float strtof(const char* str, char** endptr) - { - #ifdef NO_CPP11_STRING - return (float)cpt::stod(str); - #else - return std::strtof(str, endptr); - #endif - } +static float strtof(const char* str, char** endptr) +{ +#ifdef NO_CPP11_STRING + return (float)cpt::stod(str); +#else + return std::strtof(str, endptr); +#endif +} } #endif - diff --git a/common/timeDefs.h b/common/timeDefs.h index a6f1491b..9e6f2221 100644 --- a/common/timeDefs.h +++ b/common/timeDefs.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -20,8 +20,8 @@ #define TIME_DEFS_H #include -#include #include +#include #ifdef MACOS #include #include @@ -29,95 +29,93 @@ namespace chronos { - typedef std::chrono::system_clock clk; - typedef std::chrono::time_point time_point_clk; - typedef std::chrono::seconds sec; - typedef std::chrono::milliseconds msec; - typedef std::chrono::microseconds usec; - typedef std::chrono::nanoseconds nsec; +typedef std::chrono::system_clock clk; +typedef std::chrono::time_point time_point_clk; +typedef std::chrono::seconds sec; +typedef std::chrono::milliseconds msec; +typedef std::chrono::microseconds usec; +typedef std::chrono::nanoseconds nsec; - template - inline static void timeofday(struct timeval *tv) - { - auto now = Clock::now(); - auto millisecs = std::chrono::duration_cast(now.time_since_epoch()); - tv->tv_sec = millisecs.count()/1000; - tv->tv_usec = (millisecs.count()%1000)*1000; - } +template +inline static void timeofday(struct timeval* tv) +{ + auto now = Clock::now(); + auto millisecs = std::chrono::duration_cast(now.time_since_epoch()); + tv->tv_sec = millisecs.count() / 1000; + tv->tv_usec = (millisecs.count() % 1000) * 1000; +} - inline static void systemtimeofday(struct timeval *tv) - { - gettimeofday(tv, NULL); - //timeofday(tv); - } +inline static void systemtimeofday(struct timeval* tv) +{ + gettimeofday(tv, NULL); + // timeofday(tv); +} - inline static void addUs(timeval& tv, int us) - { - if (us < 0) - { - timeval t; - t.tv_sec = -us / 1000000; - t.tv_usec = (-us % 1000000); - timersub(&tv, &t, &tv); - return; - } - tv.tv_usec += us; - tv.tv_sec += (tv.tv_usec / 1000000); - tv.tv_usec %= 1000000; - } +inline static void addUs(timeval& tv, int us) +{ + if (us < 0) + { + timeval t; + t.tv_sec = -us / 1000000; + t.tv_usec = (-us % 1000000); + timersub(&tv, &t, &tv); + return; + } + tv.tv_usec += us; + tv.tv_sec += (tv.tv_usec / 1000000); + tv.tv_usec %= 1000000; +} - inline static long getTickCount() - { +inline static long getTickCount() +{ #ifdef MACOS - clock_serv_t cclock; - mach_timespec_t mts; - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - return mts.tv_sec*1000 + mts.tv_nsec / 1000000; + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + return mts.tv_sec * 1000 + mts.tv_nsec / 1000000; #else - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - return now.tv_sec*1000 + now.tv_nsec / 1000000; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_sec * 1000 + now.tv_nsec / 1000000; #endif - } +} - template - inline std::chrono::duration abs(std::chrono::duration d) - { - Rep x = d.count(); - return std::chrono::duration(x >= 0 ? x : -x); - } +template +inline std::chrono::duration abs(std::chrono::duration d) +{ + Rep x = d.count(); + return std::chrono::duration(x >= 0 ? x : -x); +} - template - inline int64_t duration(std::chrono::duration d) - { - return std::chrono::duration_cast(d).count(); - } +template +inline int64_t duration(std::chrono::duration d) +{ + return std::chrono::duration_cast(d).count(); +} - /// some sleep functions. Just for convenience. - template< class Rep, class Period > - inline void sleep(const std::chrono::duration& sleep_duration) - { - std::this_thread::sleep_for(sleep_duration); - } +/// some sleep functions. Just for convenience. +template +inline void sleep(const std::chrono::duration& sleep_duration) +{ + std::this_thread::sleep_for(sleep_duration); +} - inline void sleep(const int32_t& milliseconds) - { - if (milliseconds < 0) - return; - sleep(msec(milliseconds)); - } +inline void sleep(const int32_t& milliseconds) +{ + if (milliseconds < 0) + return; + sleep(msec(milliseconds)); +} - inline void usleep(const int32_t& microseconds) - { - if (microseconds < 0) - return; - sleep(usec(microseconds)); - } +inline void usleep(const int32_t& microseconds) +{ + if (microseconds < 0) + return; + sleep(usec(microseconds)); +} } #endif - - diff --git a/common/utils.h b/common/utils.h index 8d2cacc1..8c6cc256 100644 --- a/common/utils.h +++ b/common/utils.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -22,34 +22,34 @@ #include "common/strCompat.h" #include "common/utils/string_utils.h" -#include #include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include -#include +#include +#include #include #include -#include +#include +#include +#include #include +#include #include -#include +#include #ifndef FREEBSD #include #endif #include #ifdef MACOS -#include -#include #include #include +#include +#include #endif #ifdef ANDROID #include @@ -62,17 +62,17 @@ namespace strutils = utils::string; static std::string execGetOutput(const std::string& cmd) { - std::shared_ptr pipe(popen((cmd + " 2> /dev/null").c_str(), "r"), pclose); - if (!pipe) - return ""; - char buffer[1024]; - std::string result = ""; - while (!feof(pipe.get())) - { - if (fgets(buffer, 1024, pipe.get()) != NULL) - result += buffer; - } - return strutils::trim(result); + std::shared_ptr pipe(popen((cmd + " 2> /dev/null").c_str(), "r"), pclose); + if (!pipe) + return ""; + char buffer[1024]; + std::string result = ""; + while (!feof(pipe.get())) + { + if (fgets(buffer, 1024, pipe.get()) != NULL) + result += buffer; + } + return strutils::trim(result); } @@ -80,7 +80,7 @@ static std::string execGetOutput(const std::string& cmd) static std::string getProp(const std::string& key, const std::string& 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) result = cresult; return result; @@ -90,88 +90,88 @@ static std::string getProp(const std::string& key, const std::string& def = "") static std::string getOS() { - std::string os; + std::string os; #ifdef ANDROID - os = strutils::trim_copy("Android " + getProp("ro.build.version.release")); + os = strutils::trim_copy("Android " + getProp("ro.build.version.release")); #else - os = execGetOutput("lsb_release -d"); - if ((os.find(":") != std::string::npos) && (os.find("lsb_release") == std::string::npos)) - os = strutils::trim_copy(os.substr(os.find(":") + 1)); + os = execGetOutput("lsb_release -d"); + if ((os.find(":") != std::string::npos) && (os.find("lsb_release") == std::string::npos)) + os = strutils::trim_copy(os.substr(os.find(":") + 1)); #endif - if (os.empty()) - { - os = strutils::trim_copy(execGetOutput("grep /etc/os-release /etc/openwrt_release -e PRETTY_NAME -e DISTRIB_DESCRIPTION")); - if (os.find("=") != std::string::npos) - { - os = strutils::trim_copy(os.substr(os.find("=") + 1)); - os.erase(std::remove(os.begin(), os.end(), '"'), os.end()); - os.erase(std::remove(os.begin(), os.end(), '\''), os.end()); - } - } - if (os.empty()) - { - utsname u; - uname(&u); - os = u.sysname; - } - return strutils::trim_copy(os); + if (os.empty()) + { + os = strutils::trim_copy(execGetOutput("grep /etc/os-release /etc/openwrt_release -e PRETTY_NAME -e DISTRIB_DESCRIPTION")); + if (os.find("=") != std::string::npos) + { + os = strutils::trim_copy(os.substr(os.find("=") + 1)); + os.erase(std::remove(os.begin(), os.end(), '"'), os.end()); + os.erase(std::remove(os.begin(), os.end(), '\''), os.end()); + } + } + if (os.empty()) + { + utsname u; + uname(&u); + os = u.sysname; + } + return strutils::trim_copy(os); } static std::string getHostName() { #ifdef ANDROID - std::string result = getProp("net.hostname"); - if (!result.empty()) - return result; + std::string result = getProp("net.hostname"); + if (!result.empty()) + return result; #endif - char hostname[1024]; - hostname[1023] = '\0'; - gethostname(hostname, 1023); - return hostname; + char hostname[1024]; + hostname[1023] = '\0'; + gethostname(hostname, 1023); + return hostname; } static std::string getArch() { - std::string arch; + std::string arch; #ifdef ANDROID - arch = getProp("ro.product.cpu.abi"); - if (!arch.empty()) - return arch; + arch = getProp("ro.product.cpu.abi"); + if (!arch.empty()) + return arch; #endif - arch = execGetOutput("arch"); - if (arch.empty()) - arch = execGetOutput("uname -i"); - if (arch.empty() || (arch == "unknown")) - arch = execGetOutput("uname -m"); - return strutils::trim_copy(arch); + arch = execGetOutput("arch"); + if (arch.empty()) + arch = execGetOutput("uname -i"); + if (arch.empty() || (arch == "unknown")) + arch = execGetOutput("uname -m"); + return strutils::trim_copy(arch); } static long uptime() { #ifndef FREEBSD - struct sysinfo info; - sysinfo(&info); - return info.uptime; + struct sysinfo info; + sysinfo(&info); + return info.uptime; #else - std::string uptime = execGetOutput("sysctl kern.boottime"); - if ((uptime.find(" sec = ") != std::string::npos) && (uptime.find(",") != std::string::npos)) - { - uptime = strutils::trim_copy(uptime.substr(uptime.find(" sec = ") + 7)); - uptime.resize(uptime.find(",")); - timeval now; - gettimeofday(&now, NULL); - try - { - return now.tv_sec - cpt::stoul(uptime); - } - catch (...) - { - } - } - return 0; + std::string uptime = execGetOutput("sysctl kern.boottime"); + if ((uptime.find(" sec = ") != std::string::npos) && (uptime.find(",") != std::string::npos)) + { + uptime = strutils::trim_copy(uptime.substr(uptime.find(" sec = ") + 7)); + uptime.resize(uptime.find(",")); + timeval now; + gettimeofday(&now, NULL); + try + { + return now.tv_sec - cpt::stoul(uptime); + } + catch (...) + { + } + } + return 0; #endif } @@ -179,169 +179,166 @@ static long uptime() /// http://stackoverflow.com/questions/2174768/generating-random-uuids-in-linux static std::string generateUUID() { - static bool initialized(false); - if (!initialized) - { - std::srand(std::time(0)); - initialized = true; - } - std::stringstream ss; - ss << std::setfill('0') << std::hex - << 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) << std::setw(4) << (std::rand() % 0xffff); - return ss.str(); + static bool initialized(false); + if (!initialized) + { + std::srand(std::time(0)); + initialized = true; + } + std::stringstream ss; + ss << std::setfill('0') << std::hex << 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) << std::setw(4) << (std::rand() % 0xffff); + return ss.str(); } /// https://gist.github.com/OrangeTide/909204 static std::string getMacAddress(int sock) { - struct ifreq ifr; - struct ifconf ifc; - char buf[16384]; - int success = 0; + struct ifreq ifr; + struct ifconf ifc; + char buf[16384]; + int success = 0; - if (sock < 0) - return ""; + if (sock < 0) + return ""; - ifc.ifc_len = sizeof(buf); - ifc.ifc_buf = buf; - if (ioctl(sock, SIOCGIFCONF, &ifc) != 0) - return ""; + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl(sock, SIOCGIFCONF, &ifc) != 0) + return ""; - struct ifreq* it = ifc.ifc_req; - for (int i=0; iifr_addr.sa_len; + size_t len = IFNAMSIZ + it->ifr_addr.sa_len; #else - size_t len = sizeof(*it); + size_t len = sizeof(*it); #endif - strcpy(ifr.ifr_name, it->ifr_name); - if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) - { - if (!(ifr.ifr_flags & IFF_LOOPBACK)) // don't count loopback - { + strcpy(ifr.ifr_name, it->ifr_name); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) + { + if (!(ifr.ifr_flags & IFF_LOOPBACK)) // don't count loopback + { #ifdef MACOS - /// Dirty Mac version - struct ifaddrs *ifap, *ifaptr; - unsigned char *ptr; + /// Dirty Mac version + struct ifaddrs *ifap, *ifaptr; + unsigned char* ptr; - if (getifaddrs(&ifap) == 0) - { - for (ifaptr = ifap; ifaptr != NULL; ifaptr = ifaptr->ifa_next) - { -// std::cout << ifaptr->ifa_name << ", " << ifreq->ifr_name << "\n"; - if (strcmp(ifaptr->ifa_name, it->ifr_name) != 0) - continue; - if (ifaptr->ifa_addr->sa_family == AF_LINK) - { - ptr = (unsigned char *)LLADDR((struct sockaddr_dl *)ifaptr->ifa_addr); - char mac[19]; - 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) - continue; - freeifaddrs(ifap); - return mac; - } - } - freeifaddrs(ifap); - } + if (getifaddrs(&ifap) == 0) + { + for (ifaptr = ifap; ifaptr != NULL; ifaptr = ifaptr->ifa_next) + { + // std::cout << ifaptr->ifa_name << ", " << ifreq->ifr_name << "\n"; + if (strcmp(ifaptr->ifa_name, it->ifr_name) != 0) + continue; + if (ifaptr->ifa_addr->sa_family == AF_LINK) + { + ptr = (unsigned char*)LLADDR((struct sockaddr_dl*)ifaptr->ifa_addr); + char mac[19]; + 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) + continue; + freeifaddrs(ifap); + return mac; + } + } + freeifaddrs(ifap); + } #endif #ifdef FREEBSD - if (ioctl(sock, SIOCGIFMAC, &ifr) == 0) + if (ioctl(sock, SIOCGIFMAC, &ifr) == 0) #else - if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) + if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) #endif - { - success = 1; - break; - } - else - { - std::stringstream ss; - ss << "/sys/class/net/" << ifr.ifr_name << "/address"; - std::ifstream infile(ss.str().c_str()); - std::string line; - if (infile.good() && std::getline(infile, line)) - { - strutils::trim(line); - if ((line.size() == 17) && (line[2] == ':')) - return line; - } - } - } - } - else { /* handle error */ } + { + success = 1; + break; + } + else + { + std::stringstream ss; + ss << "/sys/class/net/" << ifr.ifr_name << "/address"; + std::ifstream infile(ss.str().c_str()); + std::string line; + if (infile.good() && std::getline(infile, line)) + { + strutils::trim(line); + if ((line.size() == 17) && (line[2] == ':')) + return line; + } + } + } + } + else + { /* handle error */ + } - it = (struct ifreq*)((char*)it + len); - i += len; - } + it = (struct ifreq*)((char*)it + len); + i += len; + } - if (!success) - return ""; + if (!success) + return ""; - char mac[19]; + char mac[19]; #ifndef FREEBSD - 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[2], - (unsigned char)ifr.ifr_hwaddr.sa_data[3], (unsigned char)ifr.ifr_hwaddr.sa_data[4], (unsigned char)ifr.ifr_hwaddr.sa_data[5]); + 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[2], (unsigned char)ifr.ifr_hwaddr.sa_data[3], (unsigned char)ifr.ifr_hwaddr.sa_data[4], + (unsigned char)ifr.ifr_hwaddr.sa_data[5]); #else - 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[2], - (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]); + 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[2], (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]); #endif - return mac; + return mac; } static std::string getHostId(const std::string defaultId = "") { - std::string result = strutils::trim_copy(defaultId); + std::string result = strutils::trim_copy(defaultId); - /// the Android API will return "02:00:00:00:00:00" for WifiInfo.getMacAddress(). - /// Maybe this could also happen with native code - if (!result.empty() && (result != "02:00:00:00:00:00") && (result != "00:00:00:00:00:00")) - return result; + /// the Android API will return "02:00:00:00:00:00" for WifiInfo.getMacAddress(). + /// Maybe this could also happen with native code + if (!result.empty() && (result != "02:00:00:00:00:00") && (result != "00:00:00:00:00:00")) + return result; #ifdef MACOS - /// https://stackoverflow.com/questions/933460/unique-hardware-id-in-mac-os-x - /// About this Mac, Hardware-UUID - char buf[64]; - io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); - CFStringRef uuidCf = (CFStringRef) IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); - IOObjectRelease(ioRegistryRoot); - if (CFStringGetCString(uuidCf, buf, 64, kCFStringEncodingMacRoman)) - result = buf; - CFRelease(uuidCf); + /// https://stackoverflow.com/questions/933460/unique-hardware-id-in-mac-os-x + /// About this Mac, Hardware-UUID + char buf[64]; + io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); + CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); + IOObjectRelease(ioRegistryRoot); + if (CFStringGetCString(uuidCf, buf, 64, kCFStringEncodingMacRoman)) + result = buf; + CFRelease(uuidCf); #elif ANDROID - result = getProp("ro.serialno"); + result = getProp("ro.serialno"); #endif -//#else -// // on embedded platforms it's -// // - either not there -// // - or not unique, or changes during boot -// // - or changes during boot -// std::ifstream infile("/var/lib/dbus/machine-id"); -// if (infile.good()) -// std::getline(infile, result); -//#endif - strutils::trim(result); - if (!result.empty()) - return result; + //#else + // // on embedded platforms it's + // // - either not there + // // - or not unique, or changes during boot + // // - or changes during boot + // std::ifstream infile("/var/lib/dbus/machine-id"); + // if (infile.good()) + // std::getline(infile, result); + //#endif + strutils::trim(result); + if (!result.empty()) + return result; - /// The host name should be unique enough in a LAN - return getHostName(); + /// The host name should be unique enough in a LAN + return getHostName(); } #endif - - diff --git a/common/utils/file_utils.h b/common/utils/file_utils.h index cf3c66f8..ee7a1e60 100644 --- a/common/utils/file_utils.h +++ b/common/utils/file_utils.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,12 +19,12 @@ #ifndef FILE_UTILS_H #define FILE_UTILS_H +#include "string_utils.h" +#include #include #include #include #include -#include -#include "string_utils.h" namespace utils @@ -42,55 +42,54 @@ static bool exists(const std::string& filename) static void do_chown(const std::string& file_path, const std::string& user_name, const std::string& group_name) { - if (user_name.empty() && group_name.empty()) - return; + if (user_name.empty() && group_name.empty()) + return; - if (!exists(file_path)) - return; + if (!exists(file_path)) + return; - uid_t uid = -1; - gid_t gid = -1; + uid_t uid = -1; + gid_t gid = -1; - if (!user_name.empty()) - { - struct passwd *pwd = getpwnam(user_name.c_str()); - if (pwd == NULL) - throw std::runtime_error("Failed to get uid"); - uid = pwd->pw_uid; - } + if (!user_name.empty()) + { + struct passwd* pwd = getpwnam(user_name.c_str()); + if (pwd == NULL) + throw std::runtime_error("Failed to get uid"); + uid = pwd->pw_uid; + } - if (!group_name.empty()) - { - struct group *grp = getgrnam(group_name.c_str()); - if (grp == NULL) - throw std::runtime_error("Failed to get gid"); - gid = grp->gr_gid; - } + if (!group_name.empty()) + { + struct group* grp = getgrnam(group_name.c_str()); + if (grp == NULL) + throw std::runtime_error("Failed to get gid"); + gid = grp->gr_gid; + } - if (chown(file_path.c_str(), uid, gid) == -1) - throw std::runtime_error("chown failed"); + if (chown(file_path.c_str(), uid, gid) == -1) + throw std::runtime_error("chown failed"); } -static int mkdirRecursive(const char *path, mode_t mode) +static int mkdirRecursive(const char* path, mode_t mode) { - std::vector pathes = utils::string::split(path, '/'); - std::stringstream ss; - int res = 0; - for (const auto& p: pathes) - { - if (p.empty()) - continue; - ss << "/" << p; - int res = mkdir(ss.str().c_str(), mode); - if ((res != 0) && (errno != EEXIST)) - return res; - } - return res; + std::vector pathes = utils::string::split(path, '/'); + std::stringstream ss; + int res = 0; + for (const auto& p : pathes) + { + if (p.empty()) + continue; + ss << "/" << p; + int res = mkdir(ss.str().c_str(), mode); + if ((res != 0) && (errno != EEXIST)) + return res; + } + return res; } } // namespace file } // namespace utils #endif - diff --git a/common/utils/string_utils.h b/common/utils/string_utils.h index aa2ac212..5b413eeb 100644 --- a/common/utils/string_utils.h +++ b/common/utils/string_utils.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,10 +19,10 @@ #ifndef STRING_UTILS_H #define STRING_UTILS_H -#include #include -#include #include +#include +#include #include namespace utils @@ -31,91 +31,91 @@ namespace string { // trim from start -static inline std::string <rim(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(std::isspace)))); - return s; + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + return s; } // 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(std::isspace))).base(), s.end()); - return s; + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; } // 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 -static inline std::string ltrim_copy(const std::string &s) +static inline std::string ltrim_copy(const std::string& s) { - std::string str(s); - return ltrim(str); + std::string str(s); + return ltrim(str); } // 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); - return rtrim(str); + std::string str(s); + return rtrim(str); } // 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); - return trim(str); + std::string str(s); + return trim(str); } // decode %xx to char -static std::string uriDecode(const std::string& src) { - std::string ret; - char ch; - for (size_t i=0; i(ii); - ret += ch; - i += 2; - } - else - { - ret += src[i]; - } - } - return (ret); +static std::string uriDecode(const std::string& src) +{ + std::string ret; + char ch; + for (size_t i = 0; i < src.length(); i++) + { + if (int(src[i]) == 37) + { + unsigned int ii; + sscanf(src.substr(i + 1, 2).c_str(), "%x", &ii); + ch = static_cast(ii); + ret += ch; + i += 2; + } + else + { + ret += src[i]; + } + } + return (ret); } -static std::vector &split(const std::string &s, char delim, std::vector &elems) +static std::vector& split(const std::string& s, char delim, std::vector& elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) - { - elems.push_back(item); - } - return elems; + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) + { + elems.push_back(item); + } + return elems; } -static std::vector split(const std::string &s, char delim) +static std::vector split(const std::string& s, char delim) { - std::vector elems; - split(s, delim, elems); - return elems; + std::vector elems; + split(s, delim, elems); + return elems; } } // namespace string } // namespace utils #endif - diff --git a/doc/snapcast_plain_icon.svg b/doc/snapcast_plain_icon.svg new file mode 100644 index 00000000..a6309980 --- /dev/null +++ b/doc/snapcast_plain_icon.svg @@ -0,0 +1,179 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/externals/Makefile b/externals/Makefile index a637d49d..d4974fe9 100644 --- a/externals/Makefile +++ b/externals/Makefile @@ -1,5 +1,5 @@ # 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 # it under the terms of the GNU General Public License as published by diff --git a/server/Makefile b/server/Makefile index 09b79b78..6b6a91d4 100644 --- a/server/Makefile +++ b/server/Makefile @@ -1,5 +1,5 @@ # 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 # it under the terms of the GNU General Public License as published by diff --git a/server/config.cpp b/server/config.cpp index a53b1438..46fec0a0 100644 --- a/server/config.cpp +++ b/server/config.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -17,15 +17,15 @@ ***/ #include "config.h" -#include -#include -#include -#include -#include +#include "aixlog.hpp" #include "common/snapException.h" #include "common/strCompat.h" #include "common/utils/file_utils.h" -#include "aixlog.hpp" +#include +#include +#include +#include +#include using namespace std; @@ -37,255 +37,247 @@ Config::Config() Config::~Config() { - save(); + save(); } void Config::init(const std::string& root_directory, const std::string& user, const std::string& group) { - string dir; - if (!root_directory.empty()) - dir = root_directory; - else if (getenv("HOME") == NULL) - dir = "/var/lib/snapserver/"; - else - dir = getenv("HOME"); + string dir; + if (!root_directory.empty()) + dir = root_directory; + else if (getenv("HOME") == NULL) + dir = "/var/lib/snapserver/"; + else + dir = getenv("HOME"); - if (!dir.empty() && (dir.back() != '/')) - dir += "/"; + if (!dir.empty() && (dir.back() != '/')) + dir += "/"; - if (dir.find("/var/lib/snapserver") == string::npos) - dir += ".config/snapserver/"; + if (dir.find("/var/lib/snapserver") == string::npos) + dir += ".config/snapserver/"; - int status = utils::file::mkdirRecursive(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - if ((status != 0) && (errno != EEXIST)) - throw SnapException("failed to create settings directory: \"" + dir + "\": " + cpt::to_string(errno)); + int status = utils::file::mkdirRecursive(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if ((status != 0) && (errno != EEXIST)) + throw SnapException("failed to create settings directory: \"" + dir + "\": " + cpt::to_string(errno)); - filename_ = dir + "server.json"; - SLOG(NOTICE) << "Settings file: \"" << filename_ << "\"\n"; + filename_ = dir + "server.json"; + SLOG(NOTICE) << "Settings file: \"" << filename_ << "\"\n"; - int fd; - if ((fd = open(filename_.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) - { - if (errno == EACCES) - throw std::runtime_error("failed to open file \"" + filename_ + "\", permission denied (error " + cpt::to_string(errno) + ")"); - else - throw std::runtime_error("failed to open file \"" + filename_ + "\", error " + cpt::to_string(errno)); - } - close(fd); + int fd; + if ((fd = open(filename_.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) + { + if (errno == EACCES) + throw std::runtime_error("failed to open file \"" + filename_ + "\", permission denied (error " + cpt::to_string(errno) + ")"); + else + throw std::runtime_error("failed to open file \"" + filename_ + "\", error " + cpt::to_string(errno)); + } + close(fd); - if (!user.empty() && !group.empty()) - { - try - { - utils::file::do_chown(dir, user, group); - utils::file::do_chown(filename_, user, group); - } - catch(const std::exception& e) - { - SLOG(ERROR) << "Exception in chown: " << e.what() << "\n"; - } - } + if (!user.empty() && !group.empty()) + { + try + { + utils::file::do_chown(dir, user, group); + utils::file::do_chown(filename_, user, group); + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception in chown: " << e.what() << "\n"; + } + } - try - { - ifstream ifs(filename_, std::ifstream::in); - if (ifs.good()) - { - json j; - ifs >> j; - if (j.count("ConfigVersion")) - { - json jGroups = j["Groups"]; - for (auto it = jGroups.begin(); it != jGroups.end(); ++it) - { - GroupPtr group = make_shared(); - group->fromJson(*it); -// if (client->id.empty() || getClientInfo(client->id)) -// continue; - groups.push_back(group); - } - } - } - } - catch(const std::exception& e) - { - LOG(ERROR) << "Error reading config: " << e.what() << "\n"; - } + try + { + ifstream ifs(filename_, std::ifstream::in); + if (ifs.good()) + { + json j; + ifs >> j; + if (j.count("ConfigVersion")) + { + json jGroups = j["Groups"]; + for (auto it = jGroups.begin(); it != jGroups.end(); ++it) + { + GroupPtr group = make_shared(); + group->fromJson(*it); + // if (client->id.empty() || getClientInfo(client->id)) + // continue; + groups.push_back(group); + } + } + } + } + catch (const std::exception& e) + { + LOG(ERROR) << "Error reading config: " << e.what() << "\n"; + } } void Config::save() { - if (filename_.empty()) - init(); - std::ofstream ofs(filename_.c_str(), std::ofstream::out|std::ofstream::trunc); - json clients = { - {"ConfigVersion", 2}, - {"Groups", getGroups()} - }; - ofs << std::setw(4) << clients; - ofs.close(); + if (filename_.empty()) + init(); + std::ofstream ofs(filename_.c_str(), std::ofstream::out | std::ofstream::trunc); + json clients = {{"ConfigVersion", 2}, {"Groups", getGroups()}}; + ofs << std::setw(4) << clients; + ofs.close(); } ClientInfoPtr Config::getClientInfo(const std::string& clientId) const { - if (clientId.empty()) - return nullptr; + if (clientId.empty()) + return nullptr; - for (auto group: groups) - { - for (auto client: group->clients) - { - if (client->id == clientId) - return client; - } - } + for (auto group : groups) + { + for (auto client : group->clients) + { + if (client->id == clientId) + return client; + } + } - return nullptr; + return nullptr; } GroupPtr Config::addClientInfo(ClientInfoPtr client) { - GroupPtr group = getGroupFromClient(client); - if (!group) - { - group = std::make_shared(); - group->addClient(client); - groups.push_back(group); - } - return group; + GroupPtr group = getGroupFromClient(client); + if (!group) + { + group = std::make_shared(); + group->addClient(client); + groups.push_back(group); + } + return group; } GroupPtr Config::addClientInfo(const std::string& clientId) { - ClientInfoPtr client = getClientInfo(clientId); - if (!client) - client = make_shared(clientId); - return addClientInfo(client); + ClientInfoPtr client = getClientInfo(clientId); + if (!client) + client = make_shared(clientId); + return addClientInfo(client); } GroupPtr Config::getGroup(const std::string& groupId) const { - for (auto group: groups) - { - if (group->id == groupId) - return group; - } + for (auto group : groups) + { + if (group->id == groupId) + return group; + } - return nullptr; + return nullptr; } GroupPtr Config::getGroupFromClient(const std::string& clientId) { - for (auto group: groups) - { - for (auto c: group->clients) - { - if (c->id == clientId) - return group; - } - } - return nullptr; + for (auto group : groups) + { + for (auto c : group->clients) + { + if (c->id == clientId) + return group; + } + } + return nullptr; } GroupPtr Config::getGroupFromClient(ClientInfoPtr client) { - return getGroupFromClient(client->id); + return getGroupFromClient(client->id); } json Config::getServerStatus(const json& streams) const { - Host host; - host.update(); - //TODO: Set MAC and IP - Snapserver snapserver("Snapserver", VERSION); - json serverStatus = { - {"server", { - {"host", host.toJson()},//getHostName()}, - {"snapserver", snapserver.toJson()} - }}, - {"groups", getGroups()}, - {"streams", streams} - }; + Host host; + host.update(); + // TODO: Set MAC and IP + Snapserver snapserver("Snapserver", VERSION); + json serverStatus = {{"server", + {{"host", host.toJson()}, // getHostName()}, + {"snapserver", snapserver.toJson()}}}, + {"groups", getGroups()}, + {"streams", streams}}; - return serverStatus; + return serverStatus; } json Config::getGroups() const { - json result = json::array(); - for (auto group: groups) - result.push_back(group->toJson()); - return result; + json result = json::array(); + for (auto group : groups) + result.push_back(group->toJson()); + return result; } void Config::remove(ClientInfoPtr client) { - auto group = getGroupFromClient(client); - if (!group) - return; - group->removeClient(client); - if (group->empty()) - remove(group); + auto group = getGroupFromClient(client); + if (!group) + return; + group->removeClient(client); + if (group->empty()) + remove(group); } void Config::remove(GroupPtr group, bool force) { - if (!group) - return; + if (!group) + return; - if (group->empty() || force) - groups.erase(std::remove(groups.begin(), groups.end(), group), groups.end()); + if (group->empty() || force) + groups.erase(std::remove(groups.begin(), groups.end(), group), groups.end()); } /* GroupPtr Config::removeFromGroup(const std::string& groupId, const std::string& clientId) { - GroupPtr group = getGroup(groupId); - if (!group || (group->id != groupId)) - return group; + GroupPtr group = getGroup(groupId); + if (!group || (group->id != groupId)) + return group; - auto client = getClientInfo(clientId); - if (client) - group->clients.erase(std::remove(group->clients.begin(), group->clients.end(), client), group->clients.end()); + auto client = getClientInfo(clientId); + if (client) + group->clients.erase(std::remove(group->clients.begin(), group->clients.end(), client), group->clients.end()); - addClientInfo(clientId); - return group; + addClientInfo(clientId); + return group; } GroupPtr Config::setGroupForClient(const std::string& groupId, const std::string& clientId) { - GroupPtr oldGroup = getGroupFromClient(clientId); - if (oldGroup && (oldGroup->id == groupId)) - return oldGroup; - - GroupPtr newGroup = getGroup(groupId); - if (!newGroup) - return nullptr; + GroupPtr oldGroup = getGroupFromClient(clientId); + if (oldGroup && (oldGroup->id == groupId)) + return oldGroup; - auto client = getClientInfo(clientId); - if (!client) - return nullptr; + GroupPtr newGroup = getGroup(groupId); + if (!newGroup) + return nullptr; - if (oldGroup) - removeFromGroup(oldGroup->id, clientId); + auto client = getClientInfo(clientId); + if (!client) + return nullptr; - newGroup->addClient(client); - return newGroup; + if (oldGroup) + removeFromGroup(oldGroup->id, clientId); + + newGroup->addClient(client); + return newGroup; } */ - - diff --git a/server/config.h b/server/config.h index d5bf9051..81db9cb2 100644 --- a/server/config.h +++ b/server/config.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,14 +19,14 @@ #ifndef CONFIG_H #define CONFIG_H -#include #include -#include +#include #include +#include #include "common/json.hpp" -#include "common/utils/string_utils.h" #include "common/utils.h" +#include "common/utils/string_utils.h" namespace strutils = utils::string; @@ -39,367 +39,367 @@ typedef std::shared_ptr ClientInfoPtr; typedef std::shared_ptr GroupPtr; -template +template T jGet(const json& j, const std::string& what, const T& def) { - try - { - if (!j.count(what)) - return def; - return j[what].get(); - } - catch(...) - { - return def; - } + try + { + if (!j.count(what)) + return def; + return j[what].get(); + } + catch (...) + { + return def; + } } struct Volume { - Volume(uint16_t _percent = 100, bool _muted = false) : percent(_percent), muted(_muted) - { - } + Volume(uint16_t _percent = 100, bool _muted = false) : percent(_percent), muted(_muted) + { + } - void fromJson(const json& j) - { - percent = jGet(j, "percent", percent); - muted = jGet(j, "muted", muted); - } + void fromJson(const json& j) + { + percent = jGet(j, "percent", percent); + muted = jGet(j, "muted", muted); + } - json toJson() - { - json j; - j["percent"] = percent; - j["muted"] = muted; - return j; - } + json toJson() + { + json j; + j["percent"] = percent; + j["muted"] = muted; + return j; + } - uint16_t percent; - bool muted; + uint16_t percent; + bool muted; }; struct Host { - Host() : name(""), mac(""), os(""), arch(""), ip("") - { - } + Host() : name(""), mac(""), os(""), arch(""), ip("") + { + } - void update() - { - name = getHostName(); - os = getOS(); - arch = getArch(); - } + void update() + { + name = getHostName(); + os = getOS(); + arch = getArch(); + } - void fromJson(const json& j) - { - name = strutils::trim_copy(jGet(j, "name", "")); - mac = strutils::trim_copy(jGet(j, "mac", "")); - os = strutils::trim_copy(jGet(j, "os", "")); - arch = strutils::trim_copy(jGet(j, "arch", "")); - ip = strutils::trim_copy(jGet(j, "ip", "")); - } + void fromJson(const json& j) + { + name = strutils::trim_copy(jGet(j, "name", "")); + mac = strutils::trim_copy(jGet(j, "mac", "")); + os = strutils::trim_copy(jGet(j, "os", "")); + arch = strutils::trim_copy(jGet(j, "arch", "")); + ip = strutils::trim_copy(jGet(j, "ip", "")); + } - json toJson() - { - json j; - j["name"] = name; - j["mac"] = mac; - j["os"] = os; - j["arch"] = arch; - j["ip"] = ip; - return j; - } + json toJson() + { + json j; + j["name"] = name; + j["mac"] = mac; + j["os"] = os; + j["arch"] = arch; + j["ip"] = ip; + return j; + } - std::string name; - std::string mac; - std::string os; - std::string arch; - std::string ip; + std::string name; + std::string mac; + std::string os; + std::string arch; + std::string ip; }; struct ClientConfig { - ClientConfig() : name(""), volume(100), latency(0), instance(1) - { - } + ClientConfig() : name(""), volume(100), latency(0), instance(1) + { + } - void fromJson(const json& j) - { - name = strutils::trim_copy(jGet(j, "name", "")); - volume.fromJson(j["volume"]); - latency = jGet(j, "latency", 0); - instance = jGet(j, "instance", 1); - } - - json toJson() - { - json j; - j["name"] = strutils::trim_copy(name); - j["volume"] = volume.toJson(); - j["latency"] = latency; - j["instance"] = instance; - return j; - } + void fromJson(const json& j) + { + name = strutils::trim_copy(jGet(j, "name", "")); + volume.fromJson(j["volume"]); + latency = jGet(j, "latency", 0); + instance = jGet(j, "instance", 1); + } - std::string name; - Volume volume; - int32_t latency; - size_t instance; + json toJson() + { + json j; + j["name"] = strutils::trim_copy(name); + j["volume"] = volume.toJson(); + j["latency"] = latency; + j["instance"] = instance; + return j; + } + + std::string name; + Volume volume; + int32_t latency; + size_t instance; }; struct Snapcast { - Snapcast(const std::string& _name = "", const std::string& _version = "") : name(_name), version(_version), protocolVersion(1) - { - } + Snapcast(const std::string& _name = "", const std::string& _version = "") : name(_name), version(_version), protocolVersion(1) + { + } - virtual ~Snapcast() - { - } + virtual ~Snapcast() + { + } - virtual void fromJson(const json& j) - { - name = strutils::trim_copy(jGet(j, "name", "")); - version = strutils::trim_copy(jGet(j, "version", "")); - protocolVersion = jGet(j, "protocolVersion", 1); - } + virtual void fromJson(const json& j) + { + name = strutils::trim_copy(jGet(j, "name", "")); + version = strutils::trim_copy(jGet(j, "version", "")); + protocolVersion = jGet(j, "protocolVersion", 1); + } - virtual json toJson() - { - json j; - j["name"] = strutils::trim_copy(name); - j["version"] = strutils::trim_copy(version); - j["protocolVersion"] = protocolVersion; - return j; - } + virtual json toJson() + { + json j; + j["name"] = strutils::trim_copy(name); + j["version"] = strutils::trim_copy(version); + j["protocolVersion"] = protocolVersion; + return j; + } - std::string name; - std::string version; - int protocolVersion; + std::string name; + std::string version; + int protocolVersion; }; struct Snapclient : public Snapcast { - Snapclient(const std::string& _name = "", const std::string& _version = "") : Snapcast(_name, _version) - { - } + Snapclient(const std::string& _name = "", const std::string& _version = "") : Snapcast(_name, _version) + { + } }; struct Snapserver : public Snapcast { - Snapserver(const std::string& _name = "", const std::string& _version = "") : Snapcast(_name, _version), controlProtocolVersion(1) - { - } + Snapserver(const std::string& _name = "", const std::string& _version = "") : Snapcast(_name, _version), controlProtocolVersion(1) + { + } - virtual void fromJson(const json& j) - { - Snapcast::fromJson(j); - controlProtocolVersion = jGet(j, "controlProtocolVersion", 1); - } + virtual void fromJson(const json& j) + { + Snapcast::fromJson(j); + controlProtocolVersion = jGet(j, "controlProtocolVersion", 1); + } - virtual json toJson() - { - json j = Snapcast::toJson(); - j["controlProtocolVersion"] = controlProtocolVersion; - return j; - } + virtual json toJson() + { + json j = Snapcast::toJson(); + j["controlProtocolVersion"] = controlProtocolVersion; + return j; + } - int controlProtocolVersion; + int controlProtocolVersion; }; struct ClientInfo { - ClientInfo(const std::string& _clientId = "") : id(_clientId), connected(false) - { - lastSeen.tv_sec = 0; - lastSeen.tv_usec = 0; - } + ClientInfo(const std::string& _clientId = "") : id(_clientId), connected(false) + { + lastSeen.tv_sec = 0; + lastSeen.tv_usec = 0; + } - void fromJson(const json& j) - { - host.fromJson(j["host"]); - id = jGet(j, "id", host.mac); - snapclient.fromJson(j["snapclient"]); - config.fromJson(j["config"]); - lastSeen.tv_sec = jGet(j["lastSeen"], "sec", 0); - lastSeen.tv_usec = jGet(j["lastSeen"], "usec", 0); - connected = jGet(j, "connected", true); - } + void fromJson(const json& j) + { + host.fromJson(j["host"]); + id = jGet(j, "id", host.mac); + snapclient.fromJson(j["snapclient"]); + config.fromJson(j["config"]); + lastSeen.tv_sec = jGet(j["lastSeen"], "sec", 0); + lastSeen.tv_usec = jGet(j["lastSeen"], "usec", 0); + connected = jGet(j, "connected", true); + } - json toJson() - { - json j; - j["id"] = id; - j["host"] = host.toJson(); - j["snapclient"] = snapclient.toJson(); - j["config"] = config.toJson(); - j["lastSeen"]["sec"] = lastSeen.tv_sec; - j["lastSeen"]["usec"] = lastSeen.tv_usec; - j["connected"] = connected; - return j; - } + json toJson() + { + json j; + j["id"] = id; + j["host"] = host.toJson(); + j["snapclient"] = snapclient.toJson(); + j["config"] = config.toJson(); + j["lastSeen"]["sec"] = lastSeen.tv_sec; + j["lastSeen"]["usec"] = lastSeen.tv_usec; + j["connected"] = connected; + return j; + } - std::string id; - Host host; - Snapclient snapclient; - ClientConfig config; - timeval lastSeen; - bool connected; + std::string id; + Host host; + Snapclient snapclient; + ClientConfig config; + timeval lastSeen; + bool connected; }; struct Group { - Group(const ClientInfoPtr client = nullptr) : muted(false) - { - if (client) - id = client->id; - id = generateUUID(); - } + Group(const ClientInfoPtr client = nullptr) : muted(false) + { + if (client) + id = client->id; + id = generateUUID(); + } - void fromJson(const json& j) - { - name = strutils::trim_copy(jGet(j, "name", "")); - id = strutils::trim_copy(jGet(j, "id", "")); - streamId = strutils::trim_copy(jGet(j, "stream_id", "")); - muted = jGet(j, "muted", false); - clients.clear(); - if (j.count("clients")) - { - for (auto& jClient : j["clients"]) - { - ClientInfoPtr client = std::make_shared(); - client->fromJson(jClient); - client->connected = false; - addClient(client); - } - } - } - - json toJson() - { - json j; - j["name"] = strutils::trim_copy(name); - j["id"] = strutils::trim_copy(id); - j["stream_id"] = strutils::trim_copy(streamId); - j["muted"] = muted; + void fromJson(const json& j) + { + name = strutils::trim_copy(jGet(j, "name", "")); + id = strutils::trim_copy(jGet(j, "id", "")); + streamId = strutils::trim_copy(jGet(j, "stream_id", "")); + muted = jGet(j, "muted", false); + clients.clear(); + if (j.count("clients")) + { + for (auto& jClient : j["clients"]) + { + ClientInfoPtr client = std::make_shared(); + client->fromJson(jClient); + client->connected = false; + addClient(client); + } + } + } - json jClients = json::array(); - for (auto client: clients) - jClients.push_back(client->toJson()); - j["clients"] = jClients; - return j; - } + json toJson() + { + json j; + j["name"] = strutils::trim_copy(name); + j["id"] = strutils::trim_copy(id); + j["stream_id"] = strutils::trim_copy(streamId); + j["muted"] = muted; - ClientInfoPtr removeClient(const std::string& clientId) - { - for (auto iter = clients.begin(); iter != clients.end(); ++iter) - { - if ((*iter)->id == clientId) - { - clients.erase(iter); - return (*iter); - } - } - return nullptr; - } + json jClients = json::array(); + for (auto client : clients) + jClients.push_back(client->toJson()); + j["clients"] = jClients; + return j; + } - ClientInfoPtr removeClient(ClientInfoPtr client) - { - if (!client) - return nullptr; + ClientInfoPtr removeClient(const std::string& clientId) + { + for (auto iter = clients.begin(); iter != clients.end(); ++iter) + { + if ((*iter)->id == clientId) + { + clients.erase(iter); + return (*iter); + } + } + return nullptr; + } - return removeClient(client->id); - } + ClientInfoPtr removeClient(ClientInfoPtr client) + { + if (!client) + return nullptr; - ClientInfoPtr getClient(const std::string& clientId) - { - for (auto client: clients) - { - if (client->id == clientId) - return client; - } - return nullptr; - } + return removeClient(client->id); + } - void addClient(ClientInfoPtr client) - { - if (!client) - return; + ClientInfoPtr getClient(const std::string& clientId) + { + for (auto client : clients) + { + if (client->id == clientId) + return client; + } + return nullptr; + } - for (auto c: clients) - { - if (c->id == client->id) - return; - } + void addClient(ClientInfoPtr client) + { + if (!client) + return; - clients.push_back(client); -/* sort(clients.begin(), clients.end(), - [](const ClientInfoPtr a, const ClientInfoPtr b) -> bool - { - return a.name > b.name; - }); -*/ - } + for (auto c : clients) + { + if (c->id == client->id) + return; + } - bool empty() const - { - return clients.empty(); - } + clients.push_back(client); + /* sort(clients.begin(), clients.end(), + [](const ClientInfoPtr a, const ClientInfoPtr b) -> bool + { + return a.name > b.name; + }); + */ + } - std::string name; - std::string id; - std::string streamId; - bool muted; - std::vector clients; + bool empty() const + { + return clients.empty(); + } + + std::string name; + std::string id; + std::string streamId; + bool muted; + std::vector clients; }; class Config { public: - static Config& instance() - { - static Config instance_; - return instance_; - } + static Config& instance() + { + static Config instance_; + return instance_; + } - ClientInfoPtr getClientInfo(const std::string& clientId) const; - GroupPtr addClientInfo(const std::string& clientId); - GroupPtr addClientInfo(ClientInfoPtr client); - void remove(ClientInfoPtr client); - void remove(GroupPtr group, bool force = false); + ClientInfoPtr getClientInfo(const std::string& clientId) const; + GroupPtr addClientInfo(const std::string& clientId); + GroupPtr addClientInfo(ClientInfoPtr client); + void remove(ClientInfoPtr client); + void remove(GroupPtr group, bool force = false); -// GroupPtr removeFromGroup(const std::string& groupId, const std::string& clientId); -// GroupPtr setGroupForClient(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 getGroupFromClient(const std::string& clientId); - GroupPtr getGroupFromClient(ClientInfoPtr client); - GroupPtr getGroup(const std::string& groupId) const; + GroupPtr getGroupFromClient(const std::string& clientId); + GroupPtr getGroupFromClient(ClientInfoPtr client); + GroupPtr getGroup(const std::string& groupId) const; - json getGroups() const; - json getServerStatus(const json& streams) const; + json getGroups() const; + json getServerStatus(const json& streams) const; - void save(); + void save(); - void init(const std::string& root_directory = "", const std::string& user = "", const std::string& group = ""); + void init(const std::string& root_directory = "", const std::string& user = "", const std::string& group = ""); - std::vector groups; + std::vector groups; private: - Config(); - ~Config(); - std::string filename_; + Config(); + ~Config(); + std::string filename_; }; diff --git a/server/controlServer.cpp b/server/controlServer.cpp index 0fa28a1b..8f437ea4 100644 --- a/server/controlServer.cpp +++ b/server/controlServer.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,13 +16,13 @@ along with this program. If not, see . ***/ -#include "jsonrpcpp.hpp" #include "controlServer.h" -#include "message/time.h" #include "aixlog.hpp" -#include "common/utils.h" #include "common/snapException.h" +#include "common/utils.h" #include "config.h" +#include "jsonrpcpp.hpp" +#include "message/time.h" #include using namespace std; @@ -30,173 +30,168 @@ using namespace std; using json = nlohmann::json; -ControlServer::ControlServer(asio::io_service* io_service, size_t port, ControlMessageReceiver* controlMessageReceiver) : - acceptor_v4_(nullptr), - acceptor_v6_(nullptr), - io_service_(io_service), - port_(port), - controlMessageReceiver_(controlMessageReceiver) +ControlServer::ControlServer(asio::io_service* io_service, size_t port, ControlMessageReceiver* controlMessageReceiver) + : acceptor_v4_(nullptr), acceptor_v6_(nullptr), io_service_(io_service), port_(port), controlMessageReceiver_(controlMessageReceiver) { } ControlServer::~ControlServer() { -// stop(); + // stop(); } void ControlServer::cleanup() { - std::lock_guard mlock(mutex_); - for (auto it = sessions_.begin(); it != sessions_.end(); ) - { - if (!(*it)->active()) - { - SLOG(ERROR) << "Session inactive. Removing\n"; - // don't block: remove ClientSession in a thread - auto func = [](shared_ptr s)->void{s->stop();}; - std::thread t(func, *it); - t.detach(); - //(*it)->stop(); - sessions_.erase(it++); - } - else - ++it; - } + std::lock_guard mlock(mutex_); + for (auto it = sessions_.begin(); it != sessions_.end();) + { + if (!(*it)->active()) + { + SLOG(ERROR) << "Session inactive. Removing\n"; + // don't block: remove ClientSession in a thread + auto func = [](shared_ptr s) -> void { s->stop(); }; + std::thread t(func, *it); + t.detach(); + //(*it)->stop(); + sessions_.erase(it++); + } + else + ++it; + } } void ControlServer::send(const std::string& message, const ControlSession* excludeSession) { - cleanup(); - for (auto s : sessions_) - { - if (s.get() != excludeSession) - s->sendAsync(message); - } + cleanup(); + for (auto s : sessions_) + { + if (s.get() != excludeSession) + s->sendAsync(message); + } } void ControlServer::onMessageReceived(ControlSession* connection, const std::string& message) { - std::lock_guard mlock(mutex_); - LOG(DEBUG) << "received: \"" << message << "\"\n"; - if ((message == "quit") || (message == "exit") || (message == "bye")) - { - for (auto it = sessions_.begin(); it != sessions_.end(); ++it) - { - if (it->get() == connection) - { - /// delete in a thread to avoid deadlock - auto func = [&](std::shared_ptr s)->void{sessions_.erase(s);}; - std::thread t(func, *it); - t.detach(); - break; - } - } - } - else - { - if (controlMessageReceiver_ != NULL) - controlMessageReceiver_->onMessageReceived(connection, message); - } + std::lock_guard mlock(mutex_); + LOG(DEBUG) << "received: \"" << message << "\"\n"; + if ((message == "quit") || (message == "exit") || (message == "bye")) + { + for (auto it = sessions_.begin(); it != sessions_.end(); ++it) + { + if (it->get() == connection) + { + /// delete in a thread to avoid deadlock + auto func = [&](std::shared_ptr s) -> void { sessions_.erase(s); }; + std::thread t(func, *it); + t.detach(); + break; + } + } + } + else + { + if (controlMessageReceiver_ != NULL) + controlMessageReceiver_->onMessageReceived(connection, message); + } } void ControlServer::startAccept() { - if (acceptor_v4_) - { - socket_ptr socket_v4 = make_shared(*io_service_); - acceptor_v4_->async_accept(*socket_v4, bind(&ControlServer::handleAccept, this, socket_v4)); - } - if (acceptor_v6_) - { - socket_ptr socket_v6 = make_shared(*io_service_); - acceptor_v6_->async_accept(*socket_v6, bind(&ControlServer::handleAccept, this, socket_v6)); - } + if (acceptor_v4_) + { + socket_ptr socket_v4 = make_shared(*io_service_); + acceptor_v4_->async_accept(*socket_v4, bind(&ControlServer::handleAccept, this, socket_v4)); + } + if (acceptor_v6_) + { + socket_ptr socket_v6 = make_shared(*io_service_); + acceptor_v6_->async_accept(*socket_v6, bind(&ControlServer::handleAccept, this, socket_v6)); + } } void ControlServer::handleAccept(socket_ptr socket) { - try - { - struct timeval tv; - tv.tv_sec = 5; - tv.tv_usec = 0; - setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - // socket->set_option(boost::asio::ip::tcp::no_delay(false)); - SLOG(NOTICE) << "ControlServer::NewConnection: " << socket->remote_endpoint().address().to_string() << endl; - shared_ptr session = make_shared(this, socket); - { - std::lock_guard mlock(mutex_); - session->start(); - sessions_.insert(session); - cleanup(); - } - } - catch (const std::exception& e) - { - SLOG(ERROR) << "Exception in ControlServer::handleAccept: " << e.what() << endl; - } - startAccept(); + try + { + struct timeval tv; + tv.tv_sec = 5; + tv.tv_usec = 0; + setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + // socket->set_option(boost::asio::ip::tcp::no_delay(false)); + SLOG(NOTICE) << "ControlServer::NewConnection: " << socket->remote_endpoint().address().to_string() << endl; + shared_ptr session = make_shared(this, socket); + { + std::lock_guard mlock(mutex_); + session->start(); + sessions_.insert(session); + cleanup(); + } + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception in ControlServer::handleAccept: " << e.what() << endl; + } + startAccept(); } void ControlServer::start() { - bool is_v6_only(true); - tcp::endpoint endpoint_v6(tcp::v6(), port_); - try - { - acceptor_v6_ = make_shared(*io_service_, endpoint_v6); - error_code ec; - acceptor_v6_->set_option(asio::ip::v6_only(false), ec); - asio::ip::v6_only option; - acceptor_v6_->get_option(option); - is_v6_only = option.value(); - LOG(DEBUG) << "IPv6 only: " << is_v6_only << "\n"; - } - catch (const asio::system_error& e) - { - LOG(ERROR) << "error creating TCP acceptor: " << e.what() << ", code: " << e.code() << "\n"; - } + bool is_v6_only(true); + tcp::endpoint endpoint_v6(tcp::v6(), port_); + try + { + acceptor_v6_ = make_shared(*io_service_, endpoint_v6); + error_code ec; + acceptor_v6_->set_option(asio::ip::v6_only(false), ec); + asio::ip::v6_only option; + acceptor_v6_->get_option(option); + is_v6_only = option.value(); + LOG(DEBUG) << "IPv6 only: " << is_v6_only << "\n"; + } + catch (const asio::system_error& e) + { + LOG(ERROR) << "error creating TCP acceptor: " << e.what() << ", code: " << e.code() << "\n"; + } - if (!acceptor_v6_ || is_v6_only) - { - tcp::endpoint endpoint_v4(tcp::v4(), port_); - try - { - acceptor_v4_ = make_shared(*io_service_, endpoint_v4); - } - catch (const asio::system_error& e) - { - LOG(ERROR) << "error creating TCP acceptor: " << e.what() << ", code: " << e.code() << "\n"; - } - } + if (!acceptor_v6_ || is_v6_only) + { + tcp::endpoint endpoint_v4(tcp::v4(), port_); + try + { + acceptor_v4_ = make_shared(*io_service_, endpoint_v4); + } + catch (const asio::system_error& e) + { + LOG(ERROR) << "error creating TCP acceptor: " << e.what() << ", code: " << e.code() << "\n"; + } + } - startAccept(); + startAccept(); } void ControlServer::stop() { - if (acceptor_v4_) - { - acceptor_v4_->cancel(); - acceptor_v4_ = nullptr; - } - if (acceptor_v6_) - { - acceptor_v6_->cancel(); - acceptor_v6_ = nullptr; - } - std::lock_guard mlock(mutex_); - for (auto s: sessions_) - s->stop(); + if (acceptor_v4_) + { + acceptor_v4_->cancel(); + acceptor_v4_ = nullptr; + } + if (acceptor_v6_) + { + acceptor_v6_->cancel(); + acceptor_v6_ = nullptr; + } + std::lock_guard mlock(mutex_); + for (auto s : sessions_) + s->stop(); } - diff --git a/server/controlServer.h b/server/controlServer.h index c0a49fe8..13c7eecc 100644 --- a/server/controlServer.h +++ b/server/controlServer.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -20,18 +20,18 @@ #define CONTROL_SERVER_H #include -#include -#include #include +#include #include #include -#include +#include +#include -#include "controlSession.h" #include "common/queue.h" #include "common/sampleFormat.h" -#include "message/message.h" +#include "controlSession.h" #include "message/codecHeader.h" +#include "message/message.h" #include "message/serverSettings.h" @@ -46,36 +46,34 @@ typedef std::shared_ptr socket_ptr; class ControlServer : public ControlMessageReceiver { public: - ControlServer(asio::io_service* io_service, size_t port, ControlMessageReceiver* controlMessageReceiver = NULL); - virtual ~ControlServer(); + ControlServer(asio::io_service* io_service, size_t port, ControlMessageReceiver* controlMessageReceiver = NULL); + virtual ~ControlServer(); - void start(); - void stop(); + void start(); + void stop(); - /// Send a message to all connceted clients - void send(const std::string& message, const ControlSession* excludeSession = NULL); + /// Send a message to all connceted clients + void send(const std::string& message, const ControlSession* excludeSession = NULL); - /// Clients call this when they receive a message. Implementation of MessageReceiver::onMessageReceived - virtual void onMessageReceived(ControlSession* connection, const std::string& message); + /// Clients call this when they receive a message. Implementation of MessageReceiver::onMessageReceived + virtual void onMessageReceived(ControlSession* connection, const std::string& message); private: - void startAccept(); - void handleAccept(socket_ptr socket); - void cleanup(); -// void acceptor(); - mutable std::recursive_mutex mutex_; - std::set> sessions_; - std::shared_ptr acceptor_v4_; - std::shared_ptr acceptor_v6_; + void startAccept(); + void handleAccept(socket_ptr socket); + void cleanup(); + // void acceptor(); + mutable std::recursive_mutex mutex_; + std::set> sessions_; + std::shared_ptr acceptor_v4_; + std::shared_ptr acceptor_v6_; - Queue> messages_; - asio::io_service* io_service_; - size_t port_; - ControlMessageReceiver* controlMessageReceiver_; + Queue> messages_; + asio::io_service* io_service_; + size_t port_; + ControlMessageReceiver* controlMessageReceiver_; }; #endif - - diff --git a/server/controlSession.cpp b/server/controlSession.cpp index 7fd9d963..53436bb4 100644 --- a/server/controlSession.cpp +++ b/server/controlSession.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,159 +16,158 @@ along with this program. If not, see . ***/ -#include -#include #include "controlSession.h" #include "aixlog.hpp" #include "message/pcmChunk.h" +#include +#include using namespace std; -ControlSession::ControlSession(ControlMessageReceiver* receiver, std::shared_ptr socket) : - active_(false), messageReceiver_(receiver) +ControlSession::ControlSession(ControlMessageReceiver* receiver, std::shared_ptr socket) : active_(false), messageReceiver_(receiver) { - socket_ = socket; + socket_ = socket; } ControlSession::~ControlSession() { - stop(); + stop(); } void ControlSession::start() { - { - std::lock_guard activeLock(activeMutex_); - active_ = true; - } - readerThread_ = thread(&ControlSession::reader, this); - writerThread_ = thread(&ControlSession::writer, this); + { + std::lock_guard activeLock(activeMutex_); + active_ = true; + } + readerThread_ = thread(&ControlSession::reader, this); + writerThread_ = thread(&ControlSession::writer, this); } void ControlSession::stop() { - LOG(DEBUG) << "ControlSession::stop\n"; - std::lock_guard activeLock(activeMutex_); - active_ = false; - try - { - std::error_code ec; - if (socket_) - { - std::lock_guard socketLock(socketMutex_); - socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); - if (ec) LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n"; - socket_->close(ec); - if (ec) LOG(ERROR) << "Error in socket close: " << ec.message() << "\n"; - } - if (readerThread_.joinable()) - { - LOG(DEBUG) << "ControlSession joining readerThread\n"; - readerThread_.join(); - } - if (writerThread_.joinable()) - { - LOG(DEBUG) << "ControlSession joining writerThread\n"; - messages_.abort_wait(); - writerThread_.join(); - } - } - catch(...) - { - } - socket_ = NULL; - LOG(DEBUG) << "ControlSession ControlSession stopped\n"; + LOG(DEBUG) << "ControlSession::stop\n"; + std::lock_guard activeLock(activeMutex_); + active_ = false; + try + { + std::error_code ec; + if (socket_) + { + std::lock_guard socketLock(socketMutex_); + socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); + if (ec) + LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n"; + socket_->close(ec); + if (ec) + LOG(ERROR) << "Error in socket close: " << ec.message() << "\n"; + } + if (readerThread_.joinable()) + { + LOG(DEBUG) << "ControlSession joining readerThread\n"; + readerThread_.join(); + } + if (writerThread_.joinable()) + { + LOG(DEBUG) << "ControlSession joining writerThread\n"; + messages_.abort_wait(); + writerThread_.join(); + } + } + catch (...) + { + } + socket_ = NULL; + LOG(DEBUG) << "ControlSession ControlSession stopped\n"; } void ControlSession::sendAsync(const std::string& message) { - messages_.push(message); + messages_.push(message); } bool ControlSession::send(const std::string& message) const { - //LOG(INFO) << "send: " << message << ", size: " << message.length() << "\n"; - std::lock_guard socketLock(socketMutex_); - { - std::lock_guard activeLock(activeMutex_); - if (!socket_ || !active_) - return false; - } - asio::streambuf streambuf; - std::ostream request_stream(&streambuf); - request_stream << message << "\r\n"; - asio::write(*socket_.get(), streambuf); - //LOG(INFO) << "done\n"; - return true; + // LOG(INFO) << "send: " << message << ", size: " << message.length() << "\n"; + std::lock_guard socketLock(socketMutex_); + { + std::lock_guard activeLock(activeMutex_); + if (!socket_ || !active_) + return false; + } + asio::streambuf streambuf; + std::ostream request_stream(&streambuf); + request_stream << message << "\r\n"; + asio::write(*socket_.get(), streambuf); + // LOG(INFO) << "done\n"; + return true; } void ControlSession::reader() { - try - { - std::stringstream message; - while (active_) - { - asio::streambuf response; - asio::read_until(*socket_, response, "\n"); + try + { + std::stringstream message; + while (active_) + { + asio::streambuf response; + asio::read_until(*socket_, response, "\n"); - std::string s((istreambuf_iterator(&response)), istreambuf_iterator()); - message << s; - if (s.empty() || (s.back() != '\n')) - continue; + std::string s((istreambuf_iterator(&response)), istreambuf_iterator()); + message << s; + if (s.empty() || (s.back() != '\n')) + continue; - string line; - while (std::getline(message, line, '\n')) - { - if (line.empty()) - continue; + string line; + while (std::getline(message, line, '\n')) + { + if (line.empty()) + continue; - size_t len = line.length() - 1; - if ((len >= 2) && line[len-2] == '\r') - --len; - line.resize(len); - if ((messageReceiver_ != NULL) && !line.empty()) - messageReceiver_->onMessageReceived(this, line); - } - message.str(""); - message.clear(); - } - } - catch (const std::exception& e) - { - SLOG(ERROR) << "Exception in ControlSession::reader(): " << e.what() << endl; - } - active_ = false; + size_t len = line.length() - 1; + if ((len >= 2) && line[len - 2] == '\r') + --len; + line.resize(len); + if ((messageReceiver_ != NULL) && !line.empty()) + messageReceiver_->onMessageReceived(this, line); + } + message.str(""); + message.clear(); + } + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception in ControlSession::reader(): " << e.what() << endl; + } + active_ = false; } void ControlSession::writer() { - try - { - asio::streambuf streambuf; - std::ostream stream(&streambuf); - string message; - while (active_) - { - if (messages_.try_pop(message, std::chrono::milliseconds(500))) - send(message); - } - } - catch (const std::exception& e) - { - SLOG(ERROR) << "Exception in ControlSession::writer(): " << e.what() << endl; - } - active_ = false; + try + { + asio::streambuf streambuf; + std::ostream stream(&streambuf); + string message; + while (active_) + { + if (messages_.try_pop(message, std::chrono::milliseconds(500))) + send(message); + } + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception in ControlSession::writer(): " << e.what() << endl; + } + active_ = false; } - - diff --git a/server/controlSession.h b/server/controlSession.h index 53c09e3e..499a9e76 100644 --- a/server/controlSession.h +++ b/server/controlSession.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,16 +19,16 @@ #ifndef CONTROL_SESSION_H #define CONTROL_SESSION_H +#include "common/queue.h" +#include "message/message.h" +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include "message/message.h" -#include "common/queue.h" using asio::ip::tcp; @@ -41,7 +41,7 @@ class ControlSession; class ControlMessageReceiver { public: - virtual void onMessageReceived(ControlSession* connection, const std::string& message) = 0; + virtual void onMessageReceived(ControlSession* connection, const std::string& message) = 0; }; @@ -54,42 +54,37 @@ public: class ControlSession { public: - /// ctor. Received message from the client are passed to MessageReceiver - ControlSession(ControlMessageReceiver* receiver, std::shared_ptr socket); - ~ControlSession(); - void start(); - void stop(); + /// ctor. Received message from the client are passed to MessageReceiver + ControlSession(ControlMessageReceiver* receiver, std::shared_ptr socket); + ~ControlSession(); + void start(); + void stop(); - /// Sends a message to the client (synchronous) - bool send(const std::string& message) const; + /// Sends a message to the client (synchronous) + bool send(const std::string& message) const; - /// Sends a message to the client (asynchronous) - void sendAsync(const std::string& message); + /// Sends a message to the client (asynchronous) + void sendAsync(const std::string& message); - bool active() const - { - return active_; - } + bool active() const + { + return active_; + } protected: - void reader(); - void writer(); + void reader(); + void writer(); - std::atomic active_; - mutable std::recursive_mutex activeMutex_; - mutable std::recursive_mutex socketMutex_; - std::thread readerThread_; - std::thread writerThread_; - std::shared_ptr socket_; - ControlMessageReceiver* messageReceiver_; - Queue messages_; + std::atomic active_; + mutable std::recursive_mutex activeMutex_; + mutable std::recursive_mutex socketMutex_; + std::thread readerThread_; + std::thread writerThread_; + std::shared_ptr socket_; + ControlMessageReceiver* messageReceiver_; + Queue messages_; }; - #endif - - - - diff --git a/server/encoder/encoder.h b/server/encoder/encoder.h index cfe58909..a58ed8ef 100644 --- a/server/encoder/encoder.h +++ b/server/encoder/encoder.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,12 +19,12 @@ #ifndef ENCODER_H #define ENCODER_H -#include #include +#include -#include "message/pcmChunk.h" -#include "message/codecHeader.h" #include "common/sampleFormat.h" +#include "message/codecHeader.h" +#include "message/pcmChunk.h" class Encoder; @@ -36,7 +36,7 @@ class Encoder; class EncoderListener { public: - virtual void onChunkEncoded(const Encoder* encoder, msg::PcmChunk* chunk, double duration) = 0; + virtual void onChunkEncoded(const Encoder* encoder, msg::PcmChunk* chunk, double duration) = 0; }; @@ -49,56 +49,54 @@ public: class Encoder { public: - /// ctor. Codec options (E.g. compression level) are passed as string and are codec dependend - Encoder(const std::string& codecOptions = "") : headerChunk_(NULL), codecOptions_(codecOptions) - { - } + /// ctor. Codec options (E.g. compression level) are passed as string and are codec dependend + Encoder(const std::string& codecOptions = "") : headerChunk_(NULL), codecOptions_(codecOptions) + { + } - virtual ~Encoder() - { - } + virtual ~Encoder() + { + } - /// The listener will receive the encoded stream - virtual void init(EncoderListener* listener, const SampleFormat& format) - { - if (codecOptions_ == "") - codecOptions_ = getDefaultOptions(); - listener_ = listener; - sampleFormat_ = format; - initEncoder(); - } + /// The listener will receive the encoded stream + virtual void init(EncoderListener* listener, const SampleFormat& format) + { + if (codecOptions_ == "") + codecOptions_ = getDefaultOptions(); + listener_ = listener; + sampleFormat_ = format; + initEncoder(); + } - /// Here the work is done. Encoded data is passed to the EncoderListener. - virtual void encode(const msg::PcmChunk* chunk) = 0; + /// Here the work is done. Encoded data is passed to the EncoderListener. + virtual void encode(const msg::PcmChunk* chunk) = 0; - virtual std::string name() const = 0; + virtual std::string name() const = 0; - virtual std::string getAvailableOptions() const - { - return "No codec options supported"; - } + virtual std::string getAvailableOptions() const + { + return "No codec options supported"; + } - virtual std::string getDefaultOptions() const - { - return ""; - } + virtual std::string getDefaultOptions() const + { + return ""; + } - /// Header information needed to decode the data - virtual std::shared_ptr getHeader() const - { - return headerChunk_; - } + /// Header information needed to decode the data + virtual std::shared_ptr getHeader() const + { + return headerChunk_; + } protected: - virtual void initEncoder() = 0; + virtual void initEncoder() = 0; - SampleFormat sampleFormat_; - std::shared_ptr headerChunk_; - EncoderListener* listener_; - std::string codecOptions_; + SampleFormat sampleFormat_; + std::shared_ptr headerChunk_; + EncoderListener* listener_; + std::string codecOptions_; }; #endif - - diff --git a/server/encoder/encoderFactory.cpp b/server/encoder/encoderFactory.cpp index 16377b34..7e8b166f 100644 --- a/server/encoder/encoderFactory.cpp +++ b/server/encoder/encoderFactory.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -24,9 +24,9 @@ #if defined(HAS_FLAC) #include "flacEncoder.h" #endif -#include "common/utils/string_utils.h" -#include "common/snapException.h" #include "aixlog.hpp" +#include "common/snapException.h" +#include "common/utils/string_utils.h" using namespace std; @@ -34,41 +34,38 @@ using namespace std; Encoder* EncoderFactory::createEncoder(const std::string& codecSettings) const { - Encoder* encoder; - std::string codec(codecSettings); - std::string codecOptions; - if (codec.find(":") != std::string::npos) - { - codecOptions = utils::string::trim_copy(codec.substr(codec.find(":") + 1)); - codec = utils::string::trim_copy(codec.substr(0, codec.find(":"))); - } + Encoder* encoder; + std::string codec(codecSettings); + std::string codecOptions; + if (codec.find(":") != std::string::npos) + { + codecOptions = utils::string::trim_copy(codec.substr(codec.find(":") + 1)); + codec = utils::string::trim_copy(codec.substr(0, codec.find(":"))); + } if (codec == "pcm") encoder = new PcmEncoder(codecOptions); #if defined(HAS_OGG) && defined(HAS_VORBIS) && defined(HAS_VORBISENC) else if (codec == "ogg") - encoder = new OggEncoder(codecOptions); + encoder = new OggEncoder(codecOptions); #endif #if defined(HAS_FLAC) - else if (codec == "flac") - encoder = new FlacEncoder(codecOptions); + else if (codec == "flac") + encoder = new FlacEncoder(codecOptions); #endif - else - { - throw SnapException("unknown codec: " + codec); - } + else + { + throw SnapException("unknown codec: " + codec); + } - return encoder; -/* try - { - encoder->init(NULL, format, codecOptions); - } - catch (const std::exception& e) - { - cout << "Error: " << e.what() << "\n"; - return 1; - } -*/ + return encoder; + /* try + { + encoder->init(NULL, format, codecOptions); + } + catch (const std::exception& e) + { + cout << "Error: " << e.what() << "\n"; + return 1; + } + */ } - - - diff --git a/server/encoder/encoderFactory.h b/server/encoder/encoderFactory.h index 9bdb47f2..39d2d6b0 100644 --- a/server/encoder/encoderFactory.h +++ b/server/encoder/encoderFactory.h @@ -1,14 +1,14 @@ #ifndef ENCODER_FACTORY_H #define ENCODER_FACTORY_H -#include #include "encoder.h" +#include class EncoderFactory { public: -// EncoderFactory(const std::string& codecSettings); - Encoder* createEncoder(const std::string& codecSettings) const; + // EncoderFactory(const std::string& codecSettings); + Encoder* createEncoder(const std::string& codecSettings) const; }; diff --git a/server/encoder/flacEncoder.cpp b/server/encoder/flacEncoder.cpp index 0692fd08..05e5060a 100644 --- a/server/encoder/flacEncoder.cpp +++ b/server/encoder/flacEncoder.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -18,194 +18,185 @@ #include -#include "flacEncoder.h" -#include "common/strCompat.h" -#include "common/snapException.h" #include "aixlog.hpp" +#include "common/snapException.h" +#include "common/strCompat.h" +#include "flacEncoder.h" using namespace std; FlacEncoder::FlacEncoder(const std::string& codecOptions) : Encoder(codecOptions), encoder_(NULL), pcmBufferSize_(0), encodedSamples_(0) { - flacChunk_ = new msg::PcmChunk(); - headerChunk_.reset(new msg::CodecHeader("flac")); - pcmBuffer_ = (FLAC__int32*)malloc(pcmBufferSize_ * sizeof(FLAC__int32)); + flacChunk_ = new msg::PcmChunk(); + headerChunk_.reset(new msg::CodecHeader("flac")); + pcmBuffer_ = (FLAC__int32*)malloc(pcmBufferSize_ * sizeof(FLAC__int32)); } FlacEncoder::~FlacEncoder() { - if (encoder_ != NULL) - { - FLAC__stream_encoder_finish(encoder_); - FLAC__metadata_object_delete(metadata_[0]); - FLAC__metadata_object_delete(metadata_[1]); - FLAC__stream_encoder_delete(encoder_); - } + if (encoder_ != NULL) + { + FLAC__stream_encoder_finish(encoder_); + FLAC__metadata_object_delete(metadata_[0]); + FLAC__metadata_object_delete(metadata_[1]); + FLAC__stream_encoder_delete(encoder_); + } - delete flacChunk_; - free(pcmBuffer_); + delete flacChunk_; + free(pcmBuffer_); } std::string FlacEncoder::getAvailableOptions() const { - return "compression level: [0..8]"; + return "compression level: [0..8]"; } std::string FlacEncoder::getDefaultOptions() const { - return "2"; + return "2"; } std::string FlacEncoder::name() const { - return "flac"; + return "flac"; } void FlacEncoder::encode(const msg::PcmChunk* chunk) { - int samples = chunk->getSampleCount(); - int frames = chunk->getFrameCount(); -// LOG(INFO) << "payload: " << chunk->payloadSize << "\tframes: " << frames << "\tsamples: " << samples << "\tduration: " << chunk->duration().count() << "\n"; + int samples = chunk->getSampleCount(); + int frames = chunk->getFrameCount(); + // LOG(INFO) << "payload: " << chunk->payloadSize << "\tframes: " << frames << "\tsamples: " << samples << "\tduration: " << + //chunk->duration().count() << "\n"; - if (pcmBufferSize_ < samples) - { - pcmBufferSize_ = samples; - pcmBuffer_ = (FLAC__int32*)realloc(pcmBuffer_, pcmBufferSize_ * sizeof(FLAC__int32)); - } + if (pcmBufferSize_ < samples) + { + pcmBufferSize_ = samples; + pcmBuffer_ = (FLAC__int32*)realloc(pcmBuffer_, pcmBufferSize_ * sizeof(FLAC__int32)); + } - if (sampleFormat_.sampleSize == 1) - { - FLAC__int8* buffer = (FLAC__int8*)chunk->payload; - for(int i=0; ipayload; - for(int i=0; ipayload; - for(int i=0; ipayload; + for (int i = 0; i < samples; i++) + pcmBuffer_[i] = (FLAC__int32)(buffer[i]); + } + else if (sampleFormat_.sampleSize == 2) + { + FLAC__int16* buffer = (FLAC__int16*)chunk->payload; + for (int i = 0; i < samples; i++) + pcmBuffer_[i] = (FLAC__int32)(buffer[i]); + } + else if (sampleFormat_.sampleSize == 4) + { + FLAC__int32* buffer = (FLAC__int32*)chunk->payload; + for (int i = 0; i < samples; i++) + pcmBuffer_[i] = (FLAC__int32)(buffer[i]); + } - FLAC__stream_encoder_process_interleaved(encoder_, pcmBuffer_, frames); + FLAC__stream_encoder_process_interleaved(encoder_, pcmBuffer_, frames); - if (encodedSamples_ > 0) - { - double resMs = encodedSamples_ / ((double)sampleFormat_.rate / 1000.); -// LOG(INFO) << "encoded: " << chunk->payloadSize << "\tframes: " << encodedSamples_ << "\tres: " << resMs << "\n"; - encodedSamples_ = 0; - listener_->onChunkEncoded(this, flacChunk_, resMs); - flacChunk_ = new msg::PcmChunk(chunk->format, 0); - } + if (encodedSamples_ > 0) + { + double resMs = encodedSamples_ / ((double)sampleFormat_.rate / 1000.); + // LOG(INFO) << "encoded: " << chunk->payloadSize << "\tframes: " << encodedSamples_ << "\tres: " << resMs << "\n"; + encodedSamples_ = 0; + listener_->onChunkEncoded(this, flacChunk_, resMs); + flacChunk_ = new msg::PcmChunk(chunk->format, 0); + } } -FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEncoder *encoder, - const FLAC__byte buffer[], - size_t bytes, - unsigned samples, - unsigned current_frame) +FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, + unsigned current_frame) { -// LOG(INFO) << "write_callback: " << bytes << ", " << samples << ", " << current_frame << "\n"; - if ((current_frame == 0) && (bytes > 0) && (samples == 0)) - { - headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize + bytes); - memcpy(headerChunk_->payload + headerChunk_->payloadSize, buffer, bytes); - headerChunk_->payloadSize += bytes; - } - else - { - flacChunk_->payload = (char*)realloc(flacChunk_->payload, flacChunk_->payloadSize + bytes); - memcpy(flacChunk_->payload + flacChunk_->payloadSize, buffer, bytes); - flacChunk_->payloadSize += bytes; - encodedSamples_ += samples; - } - return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; + // LOG(INFO) << "write_callback: " << bytes << ", " << samples << ", " << current_frame << "\n"; + if ((current_frame == 0) && (bytes > 0) && (samples == 0)) + { + headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize + bytes); + memcpy(headerChunk_->payload + headerChunk_->payloadSize, buffer, bytes); + headerChunk_->payloadSize += bytes; + } + else + { + flacChunk_->payload = (char*)realloc(flacChunk_->payload, flacChunk_->payloadSize + bytes); + memcpy(flacChunk_->payload + flacChunk_->payloadSize, buffer, bytes); + flacChunk_->payloadSize += bytes; + encodedSamples_ += samples; + } + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; } -FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder *encoder, - const FLAC__byte buffer[], - size_t bytes, - unsigned samples, - unsigned current_frame, - void *client_data) +FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, + unsigned current_frame, void* client_data) { - FlacEncoder* flacEncoder = (FlacEncoder*)client_data; - return flacEncoder->write_callback(encoder, buffer, bytes, samples, current_frame); + FlacEncoder* flacEncoder = (FlacEncoder*)client_data; + return flacEncoder->write_callback(encoder, buffer, bytes, samples, current_frame); } void FlacEncoder::initEncoder() { - int quality(2); - try - { - quality = cpt::stoi(codecOptions_); - } - catch(...) - { - throw SnapException("Invalid codec option: \"" + codecOptions_ + "\""); - } - if ((quality < 0) || (quality > 8)) - { - throw SnapException("compression level has to be between 0 and 8"); - } + int quality(2); + try + { + quality = cpt::stoi(codecOptions_); + } + catch (...) + { + throw SnapException("Invalid codec option: \"" + codecOptions_ + "\""); + } + if ((quality < 0) || (quality > 8)) + { + throw SnapException("compression level has to be between 0 and 8"); + } - FLAC__bool ok = true; - FLAC__StreamEncoderInitStatus init_status; - FLAC__StreamMetadata_VorbisComment_Entry entry; + FLAC__bool ok = true; + FLAC__StreamEncoderInitStatus init_status; + FLAC__StreamMetadata_VorbisComment_Entry entry; - // allocate the encoder - if ((encoder_ = FLAC__stream_encoder_new()) == NULL) - throw SnapException("error allocating encoder"); + // allocate the encoder + if ((encoder_ = FLAC__stream_encoder_new()) == NULL) + throw SnapException("error allocating encoder"); - ok &= FLAC__stream_encoder_set_verify(encoder_, true); - // compression levels (0-8): - // https://xiph.org/flac/api/group__flac__stream__encoder.html#gae49cf32f5256cb47eecd33779493ac85 - // latency: - // 0-2: 1152 frames, ~26.1224ms - // 3-8: 4096 frames, ~92.8798ms - ok &= FLAC__stream_encoder_set_compression_level(encoder_, quality); - ok &= FLAC__stream_encoder_set_channels(encoder_, sampleFormat_.channels); - ok &= FLAC__stream_encoder_set_bits_per_sample(encoder_, sampleFormat_.bits); - ok &= FLAC__stream_encoder_set_sample_rate(encoder_, sampleFormat_.rate); + ok &= FLAC__stream_encoder_set_verify(encoder_, true); + // compression levels (0-8): + // https://xiph.org/flac/api/group__flac__stream__encoder.html#gae49cf32f5256cb47eecd33779493ac85 + // latency: + // 0-2: 1152 frames, ~26.1224ms + // 3-8: 4096 frames, ~92.8798ms + ok &= FLAC__stream_encoder_set_compression_level(encoder_, quality); + ok &= FLAC__stream_encoder_set_channels(encoder_, sampleFormat_.channels); + ok &= FLAC__stream_encoder_set_bits_per_sample(encoder_, sampleFormat_.bits); + ok &= FLAC__stream_encoder_set_sample_rate(encoder_, sampleFormat_.rate); - if (!ok) - throw SnapException("error setting up encoder"); + if (!ok) + throw SnapException("error setting up encoder"); - // now add some metadata; we'll add some tags and a padding block - if ( - (metadata_[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == 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: - !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_entry_from_name_value_pair(&entry, "VERSION", VERSION) || - !FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false) - ) - throw SnapException("out of memory or tag error"); + // now add some metadata; we'll add some tags and a padding block + if ((metadata_[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == 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: + !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_entry_from_name_value_pair(&entry, "VERSION", VERSION) || + !FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false)) + throw SnapException("out of memory or tag error"); - metadata_[1]->length = 1234; // set the padding length - ok = FLAC__stream_encoder_set_metadata(encoder_, metadata_, 2); - if (!ok) - throw SnapException("error setting meta data"); + metadata_[1]->length = 1234; // set the padding length + ok = FLAC__stream_encoder_set_metadata(encoder_, metadata_, 2); + if (!ok) + throw SnapException("error setting meta data"); - // initialize encoder - init_status = FLAC__stream_encoder_init_stream(encoder_, ::write_callback, NULL, NULL, NULL, this); - if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) - throw SnapException("ERROR: initializing encoder: " + string(FLAC__StreamEncoderInitStatusString[init_status])); + // initialize encoder + init_status = FLAC__stream_encoder_init_stream(encoder_, ::write_callback, NULL, NULL, NULL, this); + if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) + throw SnapException("ERROR: initializing encoder: " + string(FLAC__StreamEncoderInitStatusString[init_status])); } - diff --git a/server/encoder/flacEncoder.h b/server/encoder/flacEncoder.h index bcdf5e72..eed3ac4a 100644 --- a/server/encoder/flacEncoder.h +++ b/server/encoder/flacEncoder.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -33,19 +33,20 @@ public: FlacEncoder(const std::string& codecOptions = ""); ~FlacEncoder(); virtual void encode(const msg::PcmChunk* chunk); - virtual std::string getAvailableOptions() const; - virtual std::string getDefaultOptions() const; - virtual std::string name() const; + virtual std::string getAvailableOptions() const; + virtual std::string getDefaultOptions() 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: virtual void initEncoder(); - FLAC__StreamEncoder *encoder_; - FLAC__StreamMetadata *metadata_[2]; + FLAC__StreamEncoder* encoder_; + FLAC__StreamMetadata* metadata_[2]; - FLAC__int32 *pcmBuffer_; + FLAC__int32* pcmBuffer_; int pcmBufferSize_; msg::PcmChunk* flacChunk_; @@ -54,5 +55,3 @@ protected: #endif - - diff --git a/server/encoder/oggEncoder.cpp b/server/encoder/oggEncoder.cpp index 2d21eca6..e3c745a0 100644 --- a/server/encoder/oggEncoder.cpp +++ b/server/encoder/oggEncoder.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,15 +16,15 @@ along with this program. If not, see . ***/ -#include #include +#include -#include "oggEncoder.h" +#include "aixlog.hpp" #include "common/snapException.h" #include "common/strCompat.h" -#include "common/utils/string_utils.h" #include "common/utils.h" -#include "aixlog.hpp" +#include "common/utils/string_utils.h" +#include "oggEncoder.h" using namespace std; @@ -36,225 +36,224 @@ OggEncoder::OggEncoder(const std::string& codecOptions) : Encoder(codecOptions), std::string OggEncoder::getAvailableOptions() const { - return "VBR:[-0.1 - 1.0]"; + return "VBR:[-0.1 - 1.0]"; } std::string OggEncoder::getDefaultOptions() const { - return "VBR:0.9"; + return "VBR:0.9"; } std::string OggEncoder::name() const { - return "ogg"; + return "ogg"; } void OggEncoder::encode(const msg::PcmChunk* chunk) { - double res = 0; - LOG(DEBUG) << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration().count() << "\n"; - int frames = chunk->getFrameCount(); - float **buffer=vorbis_analysis_buffer(&vd_, frames); + double res = 0; + LOG(DEBUG) << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration().count() + << "\n"; + int frames = chunk->getFrameCount(); + float** buffer = vorbis_analysis_buffer(&vd_, frames); - /* uninterleave samples */ - for (size_t channel = 0; channel < sampleFormat_.channels; ++channel) - { - if (sampleFormat_.sampleSize == 1) - { - int8_t* chunkBuffer = (int8_t*)chunk->payload; - for (int i=0; ipayload; - for (int i=0; ipayload; - for (int i=0; ipayload; + for (int i = 0; i < frames; i++) + buffer[channel][i] = chunkBuffer[sampleFormat_.channels * i + channel] / 128.f; + } + else if (sampleFormat_.sampleSize == 2) + { + int16_t* chunkBuffer = (int16_t*)chunk->payload; + for (int i = 0; i < frames; i++) + buffer[channel][i] = chunkBuffer[sampleFormat_.channels * i + channel] / 32768.f; + } + else if (sampleFormat_.sampleSize == 4) + { + int32_t* chunkBuffer = (int32_t*)chunk->payload; + for (int i = 0; i < frames; i++) + buffer[channel][i] = chunkBuffer[sampleFormat_.channels * i + channel] / 2147483648.f; + } + } - /* tell the library how much we actually submitted */ - vorbis_analysis_wrote(&vd_, frames); + /* tell the library how much we actually submitted */ + vorbis_analysis_wrote(&vd_, frames); - msg::PcmChunk* oggChunk = new msg::PcmChunk(chunk->format, 0); + msg::PcmChunk* oggChunk = new msg::PcmChunk(chunk->format, 0); - /* vorbis does some data preanalysis, then divvies up blocks for - more involved (potentially parallel) processing. Get a single - block for encoding now */ - size_t pos = 0; - while (vorbis_analysis_blockout(&vd_, &vb_)==1) - { - /* analysis, assume we want to use bitrate management */ - vorbis_analysis(&vb_, NULL); - vorbis_bitrate_addblock(&vb_); + /* vorbis does some data preanalysis, then divvies up blocks for + more involved (potentially parallel) processing. Get a single + block for encoding now */ + size_t pos = 0; + while (vorbis_analysis_blockout(&vd_, &vb_) == 1) + { + /* analysis, assume we want to use bitrate management */ + vorbis_analysis(&vb_, NULL); + vorbis_bitrate_addblock(&vb_); - while (vorbis_bitrate_flushpacket(&vd_, &op_)) - { - /* weld the packet into the bitstream */ - ogg_stream_packetin(&os_, &op_); + while (vorbis_bitrate_flushpacket(&vd_, &op_)) + { + /* weld the packet into the bitstream */ + ogg_stream_packetin(&os_, &op_); - /* write out pages (if any) */ - while (true) - { - int result = ogg_stream_flush(&os_, &og_); - if (result == 0) - break; - res = os_.granulepos - lastGranulepos_; + /* write out pages (if any) */ + while (true) + { + int result = ogg_stream_flush(&os_, &og_); + if (result == 0) + break; + res = os_.granulepos - lastGranulepos_; - size_t nextLen = pos + og_.header_len + og_.body_len; - // make chunk larger - if (oggChunk->payloadSize < nextLen) - oggChunk->payload = (char*)realloc(oggChunk->payload, nextLen); + size_t nextLen = pos + og_.header_len + og_.body_len; + // make chunk larger + if (oggChunk->payloadSize < nextLen) + oggChunk->payload = (char*)realloc(oggChunk->payload, nextLen); - memcpy(oggChunk->payload + pos, og_.header, og_.header_len); - pos += og_.header_len; - memcpy(oggChunk->payload + pos, og_.body, og_.body_len); - pos += og_.body_len; + memcpy(oggChunk->payload + pos, og_.header, og_.header_len); + pos += og_.header_len; + memcpy(oggChunk->payload + pos, og_.body, og_.body_len); + pos += og_.body_len; - if (ogg_page_eos(&og_)) - break; - } - } - } + if (ogg_page_eos(&og_)) + break; + } + } + } - if (res > 0) - { - res /= (sampleFormat_.rate / 1000.); - // LOG(INFO) << "res: " << res << "\n"; - lastGranulepos_ = os_.granulepos; - // make oggChunk smaller - oggChunk->payload = (char*)realloc(oggChunk->payload, pos); - oggChunk->payloadSize = pos; - listener_->onChunkEncoded(this, oggChunk, res); - } - else - delete oggChunk; + if (res > 0) + { + res /= (sampleFormat_.rate / 1000.); + // LOG(INFO) << "res: " << res << "\n"; + lastGranulepos_ = os_.granulepos; + // make oggChunk smaller + oggChunk->payload = (char*)realloc(oggChunk->payload, pos); + oggChunk->payloadSize = pos; + listener_->onChunkEncoded(this, oggChunk, res); + } + else + delete oggChunk; } void OggEncoder::initEncoder() { - if (codecOptions_.find(":") == string::npos) - throw SnapException("Invalid codec options: \"" + codecOptions_ + "\""); - string mode = utils::string::trim_copy(codecOptions_.substr(0, codecOptions_.find(":"))); - if (mode != "VBR") - throw SnapException("Unsupported codec mode: \"" + mode + "\". Available: \"VBR\""); + if (codecOptions_.find(":") == string::npos) + throw SnapException("Invalid codec options: \"" + codecOptions_ + "\""); + string mode = utils::string::trim_copy(codecOptions_.substr(0, codecOptions_.find(":"))); + if (mode != "VBR") + throw SnapException("Unsupported codec mode: \"" + mode + "\". Available: \"VBR\""); - string qual = utils::string::trim_copy(codecOptions_.substr(codecOptions_.find(":") + 1)); - double quality = 1.0; - try - { - quality = cpt::stod(qual); - } - catch(...) - { - throw SnapException("Invalid codec option: \"" + codecOptions_ + "\""); - } - if ((quality < -0.1) || (quality > 1.0)) - { - throw SnapException("compression level has to be between -0.1 and 1.0"); - } + string qual = utils::string::trim_copy(codecOptions_.substr(codecOptions_.find(":") + 1)); + double quality = 1.0; + try + { + quality = cpt::stod(qual); + } + catch (...) + { + throw SnapException("Invalid codec option: \"" + codecOptions_ + "\""); + } + if ((quality < -0.1) || (quality > 1.0)) + { + throw SnapException("compression level has to be between -0.1 and 1.0"); + } - /********** Encode setup ************/ - vorbis_info_init(&vi_); + /********** Encode setup ************/ + vorbis_info_init(&vi_); - /* choose an encoding mode. A few possibilities commented out, one - actually used: */ + /* choose an encoding mode. A few possibilities commented out, one + actually used: */ - /********************************************************************* - Encoding using a VBR quality mode. The usable range is -.1 - (lowest quality, smallest file) to 1. (highest quality, largest file). - Example quality mode .4: 44kHz stereo coupled, roughly 128kbps VBR + /********************************************************************* + Encoding using a VBR quality mode. The usable range is -.1 + (lowest quality, smallest file) to 1. (highest quality, largest file). + Example quality mode .4: 44kHz stereo coupled, roughly 128kbps VBR - ret = vorbis_encode_init_vbr(&vi,2,44100,.4); + ret = vorbis_encode_init_vbr(&vi,2,44100,.4); - --------------------------------------------------------------------- + --------------------------------------------------------------------- - Encoding using an average bitrate mode (ABR). - example: 44kHz stereo coupled, average 128kbps VBR + Encoding using an average bitrate mode (ABR). + example: 44kHz stereo coupled, average 128kbps VBR - ret = vorbis_encode_init(&vi,2,44100,-1,128000,-1); + ret = vorbis_encode_init(&vi,2,44100,-1,128000,-1); - --------------------------------------------------------------------- + --------------------------------------------------------------------- - Encode using a quality mode, but select that quality mode by asking for - an approximate bitrate. This is not ABR, it is true VBR, but selected - using the bitrate interface, and then turning bitrate management off: + Encode using a quality mode, but select that quality mode by asking for + an approximate bitrate. This is not ABR, it is true VBR, but selected + using the bitrate interface, and then turning bitrate management off: - ret = ( vorbis_encode_setup_managed(&vi,2,44100,-1,128000,-1) || - vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE2_SET,NULL) || - vorbis_encode_setup_init(&vi)); + ret = ( vorbis_encode_setup_managed(&vi,2,44100,-1,128000,-1) || + vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE2_SET,NULL) || + vorbis_encode_setup_init(&vi)); - *********************************************************************/ + *********************************************************************/ - int ret = vorbis_encode_init_vbr(&vi_, sampleFormat_.channels, sampleFormat_.rate, quality); + int ret = vorbis_encode_init_vbr(&vi_, sampleFormat_.channels, sampleFormat_.rate, quality); - /* do not continue if setup failed; this can happen if we ask for a - mode that libVorbis does not support (eg, too low a bitrate, etc, - will return 'OV_EIMPL') */ + /* do not continue if setup failed; this can happen if we ask for a + mode that libVorbis does not support (eg, too low a bitrate, etc, + will return 'OV_EIMPL') */ - if (ret) - throw SnapException("failed to init encoder"); + if (ret) + throw SnapException("failed to init encoder"); - /* add a comment */ - vorbis_comment_init(&vc_); - vorbis_comment_add_tag(&vc_, "TITLE", "SnapStream"); - vorbis_comment_add_tag(&vc_, "VERSION", VERSION); - vorbis_comment_add_tag(&vc_, "SAMPLE_FORMAT", sampleFormat_.getFormat().c_str()); + /* add a comment */ + vorbis_comment_init(&vc_); + vorbis_comment_add_tag(&vc_, "TITLE", "SnapStream"); + vorbis_comment_add_tag(&vc_, "VERSION", VERSION); + vorbis_comment_add_tag(&vc_, "SAMPLE_FORMAT", sampleFormat_.getFormat().c_str()); - /* set up the analysis state and auxiliary encoding storage */ - vorbis_analysis_init(&vd_, &vi_); - vorbis_block_init(&vd_, &vb_); + /* set up the analysis state and auxiliary encoding storage */ + vorbis_analysis_init(&vd_, &vi_); + vorbis_block_init(&vd_, &vb_); - /* set up our packet->stream encoder */ - /* pick a random serial number; that way we can more likely build - chained streams just by concatenation */ - srand(time(NULL)); - ogg_stream_init(&os_, rand()); + /* set up our packet->stream encoder */ + /* pick a random serial number; that way we can more likely build + chained streams just by concatenation */ + srand(time(NULL)); + ogg_stream_init(&os_, rand()); - /* Vorbis streams begin with three headers; the initial header (with - most of the codec setup parameters) which is mandated by the Ogg - bitstream spec. The second header holds any comment fields. The - third header holds the bitstream codebook. We merely need to - make the headers, then pass them to libvorbis one at a time; - libvorbis handles the additional Ogg bitstream constraints */ + /* Vorbis streams begin with three headers; the initial header (with + most of the codec setup parameters) which is mandated by the Ogg + bitstream spec. The second header holds any comment fields. The + third header holds the bitstream codebook. We merely need to + make the headers, then pass them to libvorbis one at a time; + libvorbis handles the additional Ogg bitstream constraints */ - ogg_packet header; - ogg_packet header_comm; - ogg_packet header_code; + ogg_packet header; + ogg_packet header_comm; + ogg_packet header_code; - vorbis_analysis_headerout(&vd_, &vc_, &header, &header_comm, &header_code); - ogg_stream_packetin(&os_, &header); - ogg_stream_packetin(&os_, &header_comm); - ogg_stream_packetin(&os_, &header_code); + vorbis_analysis_headerout(&vd_, &vc_, &header, &header_comm, &header_code); + ogg_stream_packetin(&os_, &header); + ogg_stream_packetin(&os_, &header_comm); + ogg_stream_packetin(&os_, &header_code); - /* This ensures the actual - * audio data will start on a new page, as per spec - */ - size_t pos(0); - headerChunk_.reset(new msg::CodecHeader("ogg")); - while (true) - { - int result = ogg_stream_flush(&os_, &og_); - if (result == 0) - break; - headerChunk_->payloadSize += og_.header_len + og_.body_len; - headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize); - LOG(DEBUG) << "HeadLen: " << og_.header_len << ", bodyLen: " << og_.body_len << ", result: " << result << "\n"; - memcpy(headerChunk_->payload + pos, og_.header, og_.header_len); - pos += og_.header_len; - memcpy(headerChunk_->payload + pos, og_.body, og_.body_len); - pos += og_.body_len; - } + /* This ensures the actual + * audio data will start on a new page, as per spec + */ + size_t pos(0); + headerChunk_.reset(new msg::CodecHeader("ogg")); + while (true) + { + int result = ogg_stream_flush(&os_, &og_); + if (result == 0) + break; + headerChunk_->payloadSize += og_.header_len + og_.body_len; + headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize); + LOG(DEBUG) << "HeadLen: " << og_.header_len << ", bodyLen: " << og_.body_len << ", result: " << result << "\n"; + memcpy(headerChunk_->payload + pos, og_.header, og_.header_len); + pos += og_.header_len; + memcpy(headerChunk_->payload + pos, og_.body, og_.body_len); + pos += og_.body_len; + } } - - diff --git a/server/encoder/oggEncoder.h b/server/encoder/oggEncoder.h index a9e6eebf..be5df60e 100644 --- a/server/encoder/oggEncoder.h +++ b/server/encoder/oggEncoder.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,36 +19,34 @@ #ifndef OGG_ENCODER_H #define OGG_ENCODER_H #include "encoder.h" -#include #include +#include class OggEncoder : public Encoder { public: - OggEncoder(const std::string& codecOptions = ""); - virtual void encode(const msg::PcmChunk* chunk); - virtual std::string getAvailableOptions() const; - virtual std::string getDefaultOptions() const; - virtual std::string name() const; + OggEncoder(const std::string& codecOptions = ""); + virtual void encode(const msg::PcmChunk* chunk); + virtual std::string getAvailableOptions() const; + virtual std::string getDefaultOptions() const; + virtual std::string name() const; protected: - virtual void initEncoder(); + virtual void initEncoder(); private: - ogg_stream_state os_; /// take physical pages, weld into a logical stream of packets - ogg_page og_; /// one Ogg bitstream page. Vorbis packets are inside - ogg_packet op_; /// one raw packet of data for decode + ogg_stream_state os_; /// take physical pages, weld into a logical stream of packets + ogg_page og_; /// one Ogg bitstream page. Vorbis packets are inside + ogg_packet op_; /// one raw packet of data for decode - vorbis_info vi_; /// struct that stores all the static vorbis bitstream settings - vorbis_comment vc_; /// struct that stores all the user comments + vorbis_info vi_; /// struct that stores all the static vorbis bitstream settings + vorbis_comment vc_; /// struct that stores all the user comments - vorbis_dsp_state vd_; /// central working state for the packet->PCM decoder - vorbis_block vb_; /// local working space for packet->PCM decode + vorbis_dsp_state vd_; /// central working state for the packet->PCM decoder + vorbis_block vb_; /// local working space for packet->PCM decode - ogg_int64_t lastGranulepos_; + ogg_int64_t lastGranulepos_; }; #endif - - diff --git a/server/encoder/pcmEncoder.cpp b/server/encoder/pcmEncoder.cpp index c8ee3b29..8e25e344 100644 --- a/server/encoder/pcmEncoder.cpp +++ b/server/encoder/pcmEncoder.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,54 +16,52 @@ along with this program. If not, see . ***/ -#include -#include "common/endian.hpp" #include "pcmEncoder.h" +#include "common/endian.hpp" +#include #define ID_RIFF 0x46464952 #define ID_WAVE 0x45564157 -#define ID_FMT 0x20746d66 +#define ID_FMT 0x20746d66 #define ID_DATA 0x61746164 PcmEncoder::PcmEncoder(const std::string& codecOptions) : Encoder(codecOptions) { - headerChunk_.reset(new msg::CodecHeader("pcm")); + headerChunk_.reset(new msg::CodecHeader("pcm")); } void PcmEncoder::encode(const msg::PcmChunk* chunk) { - msg::PcmChunk* pcmChunk = new msg::PcmChunk(*chunk); - listener_->onChunkEncoded(this, pcmChunk, pcmChunk->duration().count()); + msg::PcmChunk* pcmChunk = new msg::PcmChunk(*chunk); + listener_->onChunkEncoded(this, pcmChunk, pcmChunk->duration().count()); } void PcmEncoder::initEncoder() { - headerChunk_->payloadSize = 44; - headerChunk_->payload = (char*)malloc(headerChunk_->payloadSize); - char* payload = headerChunk_->payload; - assign(payload, SWAP_32(ID_RIFF)); - assign(payload + 4, SWAP_32(36)); - assign(payload + 8, SWAP_32(ID_WAVE)); - assign(payload + 12, SWAP_32(ID_FMT)); - assign(payload + 16, SWAP_32(16)); - assign(payload + 20, SWAP_16(1)); - assign(payload + 22, SWAP_16(sampleFormat_.channels)); - assign(payload + 24, SWAP_32(sampleFormat_.rate)); - assign(payload + 28, SWAP_32(sampleFormat_.rate * sampleFormat_.bits * sampleFormat_.channels / 8)); - assign(payload + 32, SWAP_16(sampleFormat_.channels * ((sampleFormat_.bits + 7) / 8))); - assign(payload + 34, SWAP_16(sampleFormat_.bits)); - assign(payload + 36, SWAP_32(ID_DATA)); - assign(payload + 40, SWAP_32(0)); + headerChunk_->payloadSize = 44; + headerChunk_->payload = (char*)malloc(headerChunk_->payloadSize); + char* payload = headerChunk_->payload; + assign(payload, SWAP_32(ID_RIFF)); + assign(payload + 4, SWAP_32(36)); + assign(payload + 8, SWAP_32(ID_WAVE)); + assign(payload + 12, SWAP_32(ID_FMT)); + assign(payload + 16, SWAP_32(16)); + assign(payload + 20, SWAP_16(1)); + assign(payload + 22, SWAP_16(sampleFormat_.channels)); + assign(payload + 24, SWAP_32(sampleFormat_.rate)); + assign(payload + 28, SWAP_32(sampleFormat_.rate * sampleFormat_.bits * sampleFormat_.channels / 8)); + assign(payload + 32, SWAP_16(sampleFormat_.channels * ((sampleFormat_.bits + 7) / 8))); + assign(payload + 34, SWAP_16(sampleFormat_.bits)); + assign(payload + 36, SWAP_32(ID_DATA)); + assign(payload + 40, SWAP_32(0)); } std::string PcmEncoder::name() const { - return "pcm"; + return "pcm"; } - - diff --git a/server/encoder/pcmEncoder.h b/server/encoder/pcmEncoder.h index e4b2d89b..0fbd3d86 100644 --- a/server/encoder/pcmEncoder.h +++ b/server/encoder/pcmEncoder.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -24,22 +24,20 @@ class PcmEncoder : public Encoder { public: - PcmEncoder(const std::string& codecOptions = ""); - virtual void encode(const msg::PcmChunk* chunk); - virtual std::string name() const; + PcmEncoder(const std::string& codecOptions = ""); + virtual void encode(const msg::PcmChunk* chunk); + virtual std::string name() const; protected: virtual void initEncoder(); - template - void assign(void* pointer, T val) - { - T* p = (T*)pointer; - *p = val; - } + template + void assign(void* pointer, T val) + { + T* p = (T*)pointer; + *p = val; + } }; #endif - - diff --git a/server/publishZeroConf/publishAvahi.cpp b/server/publishZeroConf/publishAvahi.cpp index 697b6382..fca81daf 100644 --- a/server/publishZeroConf/publishAvahi.cpp +++ b/server/publishZeroConf/publishAvahi.cpp @@ -17,222 +17,219 @@ USA. ***/ -#include -#include #include "publishAvahi.h" #include "aixlog.hpp" +#include +#include -static AvahiEntryGroup *group; -static AvahiSimplePoll *simple_poll; +static AvahiEntryGroup* group; +static AvahiSimplePoll* simple_poll; static char* name; -PublishAvahi::PublishAvahi(const std::string& serviceName) : PublishmDNS(serviceName), - client_(NULL), active_(false) +PublishAvahi::PublishAvahi(const std::string& serviceName) : PublishmDNS(serviceName), client_(NULL), active_(false) { - group = NULL; - simple_poll = NULL; - name = avahi_strdup(serviceName_.c_str()); + group = NULL; + simple_poll = NULL; + name = avahi_strdup(serviceName_.c_str()); } void PublishAvahi::publish(const std::vector& services) { - services_ = services; + services_ = services; - /// Allocate main loop object - if (!(simple_poll = avahi_simple_poll_new())) - { - ///TODO: error handling - LOG(ERROR) << "Failed to create simple poll object.\n"; - } + /// Allocate main loop object + if (!(simple_poll = avahi_simple_poll_new())) + { + /// TODO: error handling + LOG(ERROR) << "Failed to create simple poll object.\n"; + } - /// Allocate a new client - int error; - client_ = avahi_client_new(avahi_simple_poll_get(simple_poll), AVAHI_CLIENT_IGNORE_USER_CONFIG, client_callback, this, &error); + /// Allocate a new client + int error; + client_ = avahi_client_new(avahi_simple_poll_get(simple_poll), AVAHI_CLIENT_IGNORE_USER_CONFIG, client_callback, this, &error); - /// Check wether creating the client object succeeded - if (!client_) - { - LOG(ERROR) << "Failed to create client: " << avahi_strerror(error) << "\n"; - } + /// Check wether creating the client object succeeded + if (!client_) + { + LOG(ERROR) << "Failed to create client: " << avahi_strerror(error) << "\n"; + } - active_ = true; - pollThread_ = std::thread(&PublishAvahi::worker, this); + active_ = true; + pollThread_ = std::thread(&PublishAvahi::worker, this); } void PublishAvahi::worker() { - while (active_ && (avahi_simple_poll_iterate(simple_poll, 100) == 0)); + while (active_ && (avahi_simple_poll_iterate(simple_poll, 100) == 0)) + ; } PublishAvahi::~PublishAvahi() { - active_ = false; - pollThread_.join(); + active_ = false; + pollThread_.join(); - if (client_) - avahi_client_free(client_); + if (client_) + avahi_client_free(client_); - if (simple_poll) - avahi_simple_poll_free(simple_poll); + if (simple_poll) + avahi_simple_poll_free(simple_poll); - avahi_free(name); + avahi_free(name); } -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); - group = g; + assert(g == group || group == NULL); + group = g; - /// Called whenever the entry group state changes - switch (state) - { - case AVAHI_ENTRY_GROUP_ESTABLISHED : - /// The entry group has been established successfully - LOG(INFO) << "Service '" << name << "' successfully established.\n"; - break; + /// Called whenever the entry group state changes + switch (state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /// The entry group has been established successfully + LOG(INFO) << "Service '" << name << "' successfully established.\n"; + break; - case AVAHI_ENTRY_GROUP_COLLISION : - { - char *n; + case AVAHI_ENTRY_GROUP_COLLISION: + { + char* n; - /// A service name collision with a remote service happened. Let's pick a new name - n = avahi_alternative_service_name(name); - avahi_free(name); - name = n; + /// A service name collision with a remote service happened. Let's pick a new name + n = avahi_alternative_service_name(name); + avahi_free(name); + name = n; - LOG(NOTICE) << "Service name collision, renaming service to '" << name << "'\n"; + LOG(NOTICE) << "Service name collision, renaming service to '" << name << "'\n"; - /// And recreate the services - static_cast(userdata)->create_services(avahi_entry_group_get_client(g)); - break; - } + /// And recreate the services + static_cast(userdata)->create_services(avahi_entry_group_get_client(g)); + 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"; - /// Some kind of failure happened while we were registering our services - avahi_simple_poll_quit(simple_poll); - break; + /// Some kind of failure happened while we were registering our services + avahi_simple_poll_quit(simple_poll); + break; - case AVAHI_ENTRY_GROUP_UNCOMMITED: - case AVAHI_ENTRY_GROUP_REGISTERING: - ; - } + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING:; + } } -void PublishAvahi::create_services(AvahiClient *c) +void PublishAvahi::create_services(AvahiClient* c) { - assert(c); - char *n; - - /// If this is the first time we're called, let's create a new entry group if necessary - if (!group) - { - if (!(group = avahi_entry_group_new(c, entry_group_callback, this))) - { - LOG(ERROR) << "avahi_entry_group_new() failed: " << avahi_strerror(avahi_client_errno(c)) << "\n"; - goto fail; - } - } + assert(c); + char* n; - /// If the group is empty (either because it was just created, or because it was reset previously, add our entries. - int ret; - if (avahi_entry_group_is_empty(group)) - { - LOG(INFO) << "Adding service '" << name << "'\n"; + /// If this is the first time we're called, let's create a new entry group if necessary + if (!group) + { + if (!(group = avahi_entry_group_new(c, entry_group_callback, this))) + { + LOG(ERROR) << "avahi_entry_group_new() failed: " << avahi_strerror(avahi_client_errno(c)) << "\n"; + goto fail; + } + } - /// We will now add two services and one subtype to the entry group - 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_ERR_COLLISION) - goto collision; + /// If the group is empty (either because it was just created, or because it was reset previously, add our entries. + int ret; + if (avahi_entry_group_is_empty(group)) + { + LOG(INFO) << "Adding service '" << name << "'\n"; - LOG(ERROR) << "Failed to add " << service.name_ << " service: " << avahi_strerror(ret) << "\n"; - goto fail; - } - } + /// We will now add two services and one subtype to the entry group + 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_ERR_COLLISION) + goto collision; - /// 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)) - { - fprintf(stderr, "Failed to add subtype _magic._sub._printer._tcp: %s\n", avahi_strerror(ret)); - goto fail; - } -*/ - /// Tell the server to register the service - if ((ret = avahi_entry_group_commit(group)) < 0) - { - LOG(ERROR) << "Failed to commit entry group: " << avahi_strerror(ret) << "\n"; - goto fail; - } - } + LOG(ERROR) << "Failed to add " << service.name_ << " service: " << avahi_strerror(ret) << "\n"; + goto fail; + } + } - return; + /// 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)) + { + fprintf(stderr, "Failed to add subtype _magic._sub._printer._tcp: %s\n", avahi_strerror(ret)); + goto fail; + } + */ + /// Tell the server to register the service + if ((ret = avahi_entry_group_commit(group)) < 0) + { + LOG(ERROR) << "Failed to commit entry group: " << avahi_strerror(ret) << "\n"; + goto fail; + } + } + + return; collision: - /// A service name collision with a local service happened. Let's pick a new name - n = avahi_alternative_service_name(name); - avahi_free(name); - name = n; + /// A service name collision with a local service happened. Let's pick a new name + n = avahi_alternative_service_name(name); + avahi_free(name); + name = n; - LOG(INFO) << "Service name collision, renaming service to '" << name << "'\n"; + LOG(INFO) << "Service name collision, renaming service to '" << name << "'\n"; - avahi_entry_group_reset(group); + avahi_entry_group_reset(group); - create_services(c); - return; + create_services(c); + return; fail: - avahi_simple_poll_quit(simple_poll); + avahi_simple_poll_quit(simple_poll); } -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); - /// Called whenever the client or server state changes - switch (state) - { - case AVAHI_CLIENT_S_RUNNING: + /// Called whenever the client or server state changes + switch (state) + { + case AVAHI_CLIENT_S_RUNNING: - /// The server has startup successfully and registered its host name on the network, so it's time to create our services - static_cast(userdata)->create_services(c); - break; + /// The server has startup successfully and registered its host name on the network, so it's time to create our services + static_cast(userdata)->create_services(c); + break; - case AVAHI_CLIENT_FAILURE: + case AVAHI_CLIENT_FAILURE: - LOG(ERROR) << "Client failure: " << avahi_strerror(avahi_client_errno(c)) << "\n"; - avahi_simple_poll_quit(simple_poll); - break; + LOG(ERROR) << "Client failure: " << avahi_strerror(avahi_client_errno(c)) << "\n"; + avahi_simple_poll_quit(simple_poll); + break; - case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_COLLISION: - /// Let's drop our registered services. When the server is back - /// in AVAHI_SERVER_RUNNING state we will register them again with the new host name. + /// Let's drop our registered services. When the server is back + /// in AVAHI_SERVER_RUNNING state we will register them again with the new host name. - case AVAHI_CLIENT_S_REGISTERING: + case AVAHI_CLIENT_S_REGISTERING: - /// The server records are now being established. This might be caused by a host name change. We need to wait - /// for our own records to register until the host name is properly esatblished. + /// The server records are now being established. This might be caused by a host name change. We need to wait + /// for our own records to register until the host name is properly esatblished. - if (group) - avahi_entry_group_reset(group); - break; + if (group) + avahi_entry_group_reset(group); + break; - case AVAHI_CLIENT_CONNECTING: - ; - } + case AVAHI_CLIENT_CONNECTING:; + } } - - - diff --git a/server/publishZeroConf/publishAvahi.h b/server/publishZeroConf/publishAvahi.h index 4a160d2d..79e02613 100644 --- a/server/publishZeroConf/publishAvahi.h +++ b/server/publishZeroConf/publishAvahi.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -23,15 +23,15 @@ #include #include +#include #include -#include -#include #include +#include +#include #include #include -#include #include -#include +#include class PublishAvahi; @@ -40,22 +40,20 @@ class PublishAvahi; class PublishAvahi : public PublishmDNS { public: - PublishAvahi(const std::string& serviceName); - virtual ~PublishAvahi(); - virtual void publish(const std::vector& services); + PublishAvahi(const std::string& serviceName); + virtual ~PublishAvahi(); + virtual void publish(const std::vector& services); private: - 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); - void create_services(AvahiClient *c); - void worker(); - AvahiClient* client_; - std::thread pollThread_; - std::atomic active_; - std::vector services_; + 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); + void create_services(AvahiClient* c); + void worker(); + AvahiClient* client_; + std::thread pollThread_; + std::atomic active_; + std::vector services_; }; #endif - - diff --git a/server/publishZeroConf/publishBonjour.cpp b/server/publishZeroConf/publishBonjour.cpp index dec916eb..aa657c4a 100644 --- a/server/publishZeroConf/publishBonjour.cpp +++ b/server/publishZeroConf/publishBonjour.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,141 +19,143 @@ #include #include -#include "publishBonjour.h" #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) { -/// dns-sd -R Snapcast _snapcast._tcp local 1704 -/// dns-sd -R Snapcast _snapcast-jsonrpc._tcp local 1705 + /// dns-sd -R Snapcast _snapcast._tcp local 1704 + /// dns-sd -R Snapcast _snapcast-jsonrpc._tcp local 1705 } PublishBonjour::~PublishBonjour() { - active_ = false; - pollThread_.join(); - for (auto client: clients) - { - if (client) - DNSServiceRefDeallocate(client); - } + active_ = false; + pollThread_.join(); + for (auto client : clients) + { + if (client) + DNSServiceRefDeallocate(client); + } } void PublishBonjour::worker() { -// int dns_sd_fd = client ? DNSServiceRefSockFD(client) : -1; - // 1. Set up the fd_set as usual here. - // 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 - fd_set readfds; - FD_ZERO(&readfds); + // int dns_sd_fd = client ? DNSServiceRefSockFD(client) : -1; + // 1. Set up the fd_set as usual here. + // 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 + fd_set readfds; + FD_ZERO(&readfds); - std::vector dns_sd_fds; - int nfds = -1; - for (size_t n=0; n dns_sd_fds; + int nfds = -1; + for (size_t n = 0; n < clients.size(); ++n) + { + int dns_sd_fd = DNSServiceRefSockFD(clients[n]); + dns_sd_fds.push_back(dns_sd_fd); + if (nfds < dns_sd_fd) + nfds = dns_sd_fd; + // 2. Add the fd for our client(s) to the fd_set + FD_SET(dns_sd_fd, &readfds); + } + ++nfds; - // 3. Set up the timeout. - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 100*1000; + // 3. Set up the timeout. + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; - active_ = true; - while (active_) - { - FD_ZERO(&readfds); - for (size_t n=0; n 0) - { + int result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv); + if (result > 0) + { - for (size_t n=0; n& services) { - for (auto service: services) - { - DNSServiceFlags flags = 0; - Opaque16 registerPort = { { static_cast(service.port_ >> 8), static_cast(service.port_ & 0xFF) } }; - 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, registerPort.NotAnInteger, 0, NULL, reg_reply, this); - clients.push_back(client); - } + for (auto service : services) + { + DNSServiceFlags flags = 0; + Opaque16 registerPort = {{static_cast(service.port_ >> 8), static_cast(service.port_ & 0xFF)}}; + 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, registerPort.NotAnInteger, 0, + NULL, reg_reply, this); + clients.push_back(client); + } - pollThread_ = std::thread(&PublishBonjour::worker, this); + pollThread_ = std::thread(&PublishBonjour::worker, this); } - - - - diff --git a/server/publishZeroConf/publishBonjour.h b/server/publishZeroConf/publishBonjour.h index 8926e80b..66d58a4a 100644 --- a/server/publishZeroConf/publishBonjour.h +++ b/server/publishZeroConf/publishBonjour.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -20,8 +20,8 @@ #ifndef PUBLISH_BONJOUR_H #define PUBLISH_BONJOUR_H -#include #include +#include class PublishBonjour; @@ -30,18 +30,16 @@ class PublishBonjour; class PublishBonjour : public PublishmDNS { public: - PublishBonjour(const std::string& serviceName); - virtual ~PublishBonjour(); - virtual void publish(const std::vector& services); + PublishBonjour(const std::string& serviceName); + virtual ~PublishBonjour(); + virtual void publish(const std::vector& services); private: - std::thread pollThread_; - void worker(); - std::atomic active_; + std::thread pollThread_; + void worker(); + std::atomic active_; std::vector clients; }; #endif - - diff --git a/server/publishZeroConf/publishmDNS.h b/server/publishZeroConf/publishmDNS.h old mode 100755 new mode 100644 index 9ad667d7..affd26c4 --- a/server/publishZeroConf/publishmDNS.h +++ b/server/publishZeroConf/publishmDNS.h @@ -7,30 +7,30 @@ struct mDNSService { - mDNSService(const std::string& name, size_t port) : name_(name), port_(port) - { - } + mDNSService(const std::string& name, size_t port) : name_(name), port_(port) + { + } - std::string name_; - size_t port_; + std::string name_; + size_t port_; }; class PublishmDNS { public: - PublishmDNS(const std::string& serviceName) : serviceName_(serviceName) - { - } + PublishmDNS(const std::string& serviceName) : serviceName_(serviceName) + { + } - virtual ~PublishmDNS() - { - } + virtual ~PublishmDNS() + { + } - virtual void publish(const std::vector& services) = 0; + virtual void publish(const std::vector& services) = 0; protected: - std::string serviceName_; + std::string serviceName_; }; #if defined(HAS_AVAHI) diff --git a/server/snapServer.cpp b/server/snapServer.cpp index bb98ce10..5f470c68 100644 --- a/server/snapServer.cpp +++ b/server/snapServer.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -24,19 +24,19 @@ #ifdef HAS_DAEMON #include "common/daemon.h" #endif -#include "common/timeDefs.h" -#include "common/utils/string_utils.h" +#include "common/sampleFormat.h" #include "common/signalHandler.h" #include "common/snapException.h" -#include "common/sampleFormat.h" -#include "message/message.h" +#include "common/timeDefs.h" +#include "common/utils/string_utils.h" #include "encoder/encoderFactory.h" +#include "message/message.h" #include "streamServer.h" #if defined(HAS_AVAHI) || defined(HAS_BONJOUR) #include "publishZeroConf/publishmDNS.h" #endif -#include "config.h" #include "aixlog.hpp" +#include "config.h" volatile sig_atomic_t g_terminated = false; @@ -52,178 +52,178 @@ int main(int argc, char* argv[]) #pragma message "Warning: the macOS support is experimental and might not be maintained" #endif - int exitcode = EXIT_SUCCESS; - try - { - StreamServerSettings settings; - std::string pcmStream = "pipe:///tmp/snapfifo?name=default"; + int exitcode = EXIT_SUCCESS; + try + { + StreamServerSettings settings; + std::string pcmStream = "pipe:///tmp/snapfifo?name=default"; - OptionParser op("Allowed options"); - auto helpSwitch = op.add("h", "help", "Produce help message"); - auto groffSwitch = op.add("", "groff", "produce groff message"); - auto debugOption = op.add, Attribute::hidden>("", "debug", "enable debug logging", ""); - auto versionSwitch = op.add("v", "version", "Show version number"); - /*auto portValue =*/ op.add>("p", "port", "Server port", settings.port, &settings.port); - /*auto controlPortValue =*/ op.add>("", "controlPort", "Remote control port", settings.controlPort, &settings.controlPort); - auto streamValue = op.add>("s", "stream", "URI of the PCM input stream.\nFormat: TYPE://host/path?name=NAME\n[&codec=CODEC]\n[&sampleformat=SAMPLEFORMAT]", pcmStream, &pcmStream); + OptionParser op("Allowed options"); + auto helpSwitch = op.add("h", "help", "Produce help message"); + auto groffSwitch = op.add("", "groff", "produce groff message"); + auto debugOption = op.add, Attribute::hidden>("", "debug", "enable debug logging", ""); + auto versionSwitch = op.add("v", "version", "Show version number"); + /*auto portValue =*/op.add>("p", "port", "Server port", settings.port, &settings.port); + /*auto controlPortValue =*/op.add>("", "controlPort", "Remote control port", settings.controlPort, &settings.controlPort); + auto streamValue = op.add>( + "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>("", "sampleformat", "Default sample format", settings.sampleFormat, &settings.sampleFormat); - /*auto codecValue =*/ op.add>("c", "codec", "Default transport codec\n(flac|ogg|pcm)[:options]\nType codec:? to get codec specific options", settings.codec, &settings.codec); - /*auto streamBufferValue =*/ op.add>("", "streamBuffer", "Default stream read buffer [ms]", settings.streamReadMs, &settings.streamReadMs); - /*auto bufferValue =*/ op.add>("b", "buffer", "Buffer [ms]", settings.bufferMs, &settings.bufferMs); - /*auto muteSwitch =*/ op.add("", "sendToMuted", "Send audio to muted clients", &settings.sendAudioToMutedClients); + /*auto sampleFormatValue =*/op.add>("", "sampleformat", "Default sample format", settings.sampleFormat, &settings.sampleFormat); + /*auto codecValue =*/op.add>( + "c", "codec", "Default transport codec\n(flac|ogg|pcm)[:options]\nType codec:? to get codec specific options", settings.codec, &settings.codec); + /*auto streamBufferValue =*/op.add>("", "streamBuffer", "Default stream read buffer [ms]", settings.streamReadMs, &settings.streamReadMs); + /*auto bufferValue =*/op.add>("b", "buffer", "Buffer [ms]", settings.bufferMs, &settings.bufferMs); + /*auto muteSwitch =*/op.add("", "sendToMuted", "Send audio to muted clients", &settings.sendAudioToMutedClients); #ifdef HAS_DAEMON - int processPriority(0); - auto daemonOption = op.add>("d", "daemon", "Daemonize\noptional process priority [-20..19]", 0, &processPriority); - auto userValue = op.add>("", "user", "the user[:group] to run snapserver as when daemonized", ""); + int processPriority(0); + auto daemonOption = op.add>("d", "daemon", "Daemonize\noptional process priority [-20..19]", 0, &processPriority); + auto userValue = op.add>("", "user", "the user[:group] to run snapserver as when daemonized", ""); #endif - try - { - op.parse(argc, argv); - } - catch (const std::invalid_argument& e) - { - SLOG(ERROR) << "Exception: " << e.what() << std::endl; - cout << "\n" << op << "\n"; - exit(EXIT_FAILURE); - } + try + { + op.parse(argc, argv); + } + catch (const std::invalid_argument& e) + { + SLOG(ERROR) << "Exception: " << e.what() << std::endl; + cout << "\n" << op << "\n"; + exit(EXIT_FAILURE); + } - if (versionSwitch->is_set()) - { - cout << "snapserver v" << VERSION << "\n" - << "Copyright (C) 2014-2018 BadAix (snapcast@badaix.de).\n" - << "License GPLv3+: GNU GPL version 3 or later .\n" - << "This is free software: you are free to change and redistribute it.\n" - << "There is NO WARRANTY, to the extent permitted by law.\n\n" - << "Written by Johannes Pohl.\n"; - exit(EXIT_SUCCESS); - } + if (versionSwitch->is_set()) + { + cout << "snapserver v" << VERSION << "\n" + << "Copyright (C) 2014-2018 BadAix (snapcast@badaix.de).\n" + << "License GPLv3+: GNU GPL version 3 or later .\n" + << "This is free software: you are free to change and redistribute it.\n" + << "There is NO WARRANTY, to the extent permitted by law.\n\n" + << "Written by Johannes Pohl.\n"; + exit(EXIT_SUCCESS); + } - if (helpSwitch->is_set()) - { - cout << op << "\n"; - exit(EXIT_SUCCESS); - } + if (helpSwitch->is_set()) + { + cout << op << "\n"; + exit(EXIT_SUCCESS); + } - if (groffSwitch->is_set()) - { - GroffOptionPrinter option_printer(&op); - cout << option_printer.print(); - exit(EXIT_SUCCESS); - } + if (groffSwitch->is_set()) + { + GroffOptionPrinter option_printer(&op); + cout << option_printer.print(); + exit(EXIT_SUCCESS); + } - if (!streamValue->is_set()) - settings.pcmStreams.push_back(streamValue->value()); + if (!streamValue->is_set()) + settings.pcmStreams.push_back(streamValue->value()); - for (size_t n=0; ncount(); ++n) - { - cout << streamValue->value(n) << "\n"; - settings.pcmStreams.push_back(streamValue->value(n)); - } + for (size_t n = 0; n < streamValue->count(); ++n) + { + cout << streamValue->value(n) << "\n"; + settings.pcmStreams.push_back(streamValue->value(n)); + } - if (settings.codec.find(":?") != string::npos) - { - EncoderFactory encoderFactory; - std::unique_ptr encoder(encoderFactory.createEncoder(settings.codec)); - if (encoder) - { - cout << "Options for codec \"" << encoder->name() << "\":\n" - << " " << encoder->getAvailableOptions() << "\n" - << " Default: \"" << encoder->getDefaultOptions() << "\"\n"; - } - return 1; - } + if (settings.codec.find(":?") != string::npos) + { + EncoderFactory encoderFactory; + std::unique_ptr encoder(encoderFactory.createEncoder(settings.codec)); + if (encoder) + { + cout << "Options for codec \"" << encoder->name() << "\":\n" + << " " << encoder->getAvailableOptions() << "\n" + << " Default: \"" << encoder->getDefaultOptions() << "\"\n"; + } + return 1; + } - AixLog::Log::init("snapserver", AixLog::Severity::trace, AixLog::Type::special); - if (debugOption->is_set()) - { - AixLog::Log::instance().add_logsink(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); - if (!debugOption->value().empty()) - AixLog::Log::instance().add_logsink(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(), "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); - } - else - { - AixLog::Log::instance().add_logsink(AixLog::Severity::info, AixLog::Type::all, "%Y-%m-%d %H-%M-%S [#severity]"); - } + AixLog::Log::init("snapserver", AixLog::Severity::trace, AixLog::Type::special); + if (debugOption->is_set()) + { + AixLog::Log::instance().add_logsink(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); + if (!debugOption->value().empty()) + AixLog::Log::instance().add_logsink(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(), + "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); + } + else + { + AixLog::Log::instance().add_logsink(AixLog::Severity::info, AixLog::Type::all, "%Y-%m-%d %H-%M-%S [#severity]"); + } - signal(SIGHUP, signal_handler); - signal(SIGTERM, signal_handler); - signal(SIGINT, signal_handler); + signal(SIGHUP, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); #ifdef HAS_DAEMON - std::unique_ptr daemon; - if (daemonOption->is_set()) - { - string user = ""; - string group = ""; + std::unique_ptr daemon; + if (daemonOption->is_set()) + { + string user = ""; + string group = ""; - if (userValue->is_set()) - { - if (userValue->value().empty()) - std::invalid_argument("user must not be empty"); + if (userValue->is_set()) + { + if (userValue->value().empty()) + std::invalid_argument("user must not be empty"); - vector user_group = utils::string::split(userValue->value(), ':'); - user = user_group[0]; - if (user_group.size() > 1) - group = user_group[1]; - } + vector user_group = utils::string::split(userValue->value(), ':'); + user = user_group[0]; + if (user_group.size() > 1) + group = user_group[1]; + } - Config::instance().init("/var/lib/snapserver", user, group); - daemon.reset(new Daemon(user, group, "/var/run/snapserver/pid")); - daemon->daemonize(); - if (processPriority < -20) - processPriority = -20; - else if (processPriority > 19) - processPriority = 19; - if (processPriority != 0) - setpriority(PRIO_PROCESS, 0, processPriority); - SLOG(NOTICE) << "daemon started" << std::endl; - } - else - Config::instance().init(); + Config::instance().init("/var/lib/snapserver", user, group); + daemon.reset(new Daemon(user, group, "/var/run/snapserver/pid")); + daemon->daemonize(); + if (processPriority < -20) + processPriority = -20; + else if (processPriority > 19) + processPriority = 19; + if (processPriority != 0) + setpriority(PRIO_PROCESS, 0, processPriority); + SLOG(NOTICE) << "daemon started" << std::endl; + } + else + Config::instance().init(); #else - Config::instance().init(); + Config::instance().init(); #endif #if defined(HAS_AVAHI) || defined(HAS_BONJOUR) - PublishZeroConf publishZeroConfg("Snapcast"); - publishZeroConfg.publish({ - mDNSService("_snapcast._tcp", settings.port), - mDNSService("_snapcast-jsonrpc._tcp", settings.controlPort), - mDNSService("_snapcastjsonrpc._tcp", settings.controlPort) - }); + PublishZeroConf publishZeroConfg("Snapcast"); + publishZeroConfg.publish({mDNSService("_snapcast._tcp", settings.port), mDNSService("_snapcast-jsonrpc._tcp", settings.controlPort), + mDNSService("_snapcastjsonrpc._tcp", settings.controlPort)}); #endif - if (settings.bufferMs < 400) - settings.bufferMs = 400; + if (settings.bufferMs < 400) + settings.bufferMs = 400; - asio::io_service io_service; - std::unique_ptr streamServer(new StreamServer(&io_service, settings)); - streamServer->start(); + asio::io_service io_service; + std::unique_ptr streamServer(new StreamServer(&io_service, settings)); + streamServer->start(); - auto func = [](asio::io_service* ioservice)->void{ioservice->run();}; - std::thread t(func, &io_service); + auto func = [](asio::io_service* ioservice) -> void { ioservice->run(); }; + std::thread t(func, &io_service); - while (!g_terminated) - chronos::sleep(100); + while (!g_terminated) + chronos::sleep(100); - io_service.stop(); - t.join(); + io_service.stop(); + t.join(); - LOG(INFO) << "Stopping streamServer" << endl; - streamServer->stop(); - LOG(INFO) << "done" << endl; - } - catch (const std::exception& e) - { - SLOG(ERROR) << "Exception: " << e.what() << std::endl; - exitcode = EXIT_FAILURE; - } + LOG(INFO) << "Stopping streamServer" << endl; + streamServer->stop(); + LOG(INFO) << "done" << endl; + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception: " << e.what() << std::endl; + exitcode = EXIT_FAILURE; + } - SLOG(NOTICE) << "daemon terminated." << endl; - exit(exitcode); + SLOG(NOTICE) << "daemon terminated." << endl; + exit(exitcode); } - diff --git a/server/streamServer.cpp b/server/streamServer.cpp index 8f1be7e8..4bac4070 100644 --- a/server/streamServer.cpp +++ b/server/streamServer.cpp @@ -17,11 +17,11 @@ ***/ #include "streamServer.h" -#include "message/time.h" -#include "message/hello.h" -#include "message/streamTags.h" #include "aixlog.hpp" #include "config.h" +#include "message/hello.h" +#include "message/streamTags.h" +#include "message/time.h" #include using namespace std; @@ -29,11 +29,8 @@ using namespace std; using json = nlohmann::json; -StreamServer::StreamServer(asio::io_service* io_service, const StreamServerSettings& streamServerSettings) : - io_service_(io_service), - acceptor_v4_(nullptr), - acceptor_v6_(nullptr), - settings_(streamServerSettings) +StreamServer::StreamServer(asio::io_service* io_service, const StreamServerSettings& streamServerSettings) + : io_service_(io_service), acceptor_v4_(nullptr), acceptor_v6_(nullptr), settings_(streamServerSettings) { } @@ -43,730 +40,823 @@ 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"...}} - - // Send meta to all connected clients - const auto meta = pcmStream->getMeta(); - //cout << "metadata = " << meta->msg.dump(3) << "\n"; + /// Notification: {"jsonrpc":"2.0","method":"Stream.OnMetadata","params":{"id":"stream 1", "meta": {"album": "some album", "artist": "some artist", "track": + /// "some track"...}} - for (auto s : sessions_) - { - if (s->pcmStream().get() == pcmStream) - s->sendAsync(meta); - } + // Send meta to all connected clients + const auto meta = pcmStream->getMeta(); + // cout << "metadata = " << meta->msg.dump(3) << "\n"; - LOG(INFO) << "onMetaChanged (" << pcmStream->getName() << ")\n"; - json notification = jsonrpcpp::Notification("Stream.OnMetadata", jsonrpcpp::Parameter("id", pcmStream->getId(), "meta", meta->msg)).to_json(); - controlServer_->send(notification.dump(), NULL); - ////cout << "Notification: " << notification.dump() << "\n"; + for (auto s : sessions_) + { + if (s->pcmStream().get() == pcmStream) + s->sendAsync(meta); + } + + LOG(INFO) << "onMetaChanged (" << pcmStream->getName() << ")\n"; + json notification = jsonrpcpp::Notification("Stream.OnMetadata", jsonrpcpp::Parameter("id", pcmStream->getId(), "meta", meta->msg)).to_json(); + controlServer_->send(notification.dump(), NULL); + ////cout << "Notification: " << notification.dump() << "\n"; } 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"}}}} - LOG(INFO) << "onStateChanged (" << pcmStream->getName() << "): " << state << "\n"; -// LOG(INFO) << pcmStream->toJson().dump(4); - json notification = jsonrpcpp::Notification("Stream.OnUpdate", jsonrpcpp::Parameter("id", pcmStream->getId(), "stream", pcmStream->toJson())).to_json(); - controlServer_->send(notification.dump(), NULL); - ////cout << "Notification: " << notification.dump() << "\n"; + /// 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) << pcmStream->toJson().dump(4); + json notification = jsonrpcpp::Notification("Stream.OnUpdate", jsonrpcpp::Parameter("id", pcmStream->getId(), "stream", pcmStream->toJson())).to_json(); + controlServer_->send(notification.dump(), NULL); + ////cout << "Notification: " << notification.dump() << "\n"; } void StreamServer::onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration) { -// LOG(INFO) << "onChunkRead (" << pcmStream->getName() << "): " << duration << "ms\n"; - bool isDefaultStream(pcmStream == streamManager_->getDefaultStream().get()); + // LOG(INFO) << "onChunkRead (" << pcmStream->getName() << "): " << duration << "ms\n"; + bool isDefaultStream(pcmStream == streamManager_->getDefaultStream().get()); - msg::message_ptr shared_message(chunk); - std::lock_guard mlock(sessionsMutex_); - for (auto s : sessions_) - { - if (!settings_.sendAudioToMutedClients) - { - GroupPtr group = Config::instance().getGroupFromClient(s->clientId); - if (group) - { - if (group->muted) - continue; + msg::message_ptr shared_message(chunk); + std::lock_guard mlock(sessionsMutex_); + for (auto s : sessions_) + { + if (!settings_.sendAudioToMutedClients) + { + GroupPtr group = Config::instance().getGroupFromClient(s->clientId); + if (group) + { + if (group->muted) + continue; - ClientInfoPtr client = group->getClient(s->clientId); - if (client && client->config.volume.muted) - continue; - } - } + ClientInfoPtr client = group->getClient(s->clientId); + if (client && client->config.volume.muted) + continue; + } + } - if (!s->pcmStream() && isDefaultStream)//->getName() == "default") - s->sendAsync(shared_message); - else if (s->pcmStream().get() == pcmStream) - s->sendAsync(shared_message); - } + if (!s->pcmStream() && isDefaultStream) //->getName() == "default") + s->sendAsync(shared_message); + else if (s->pcmStream().get() == pcmStream) + s->sendAsync(shared_message); + } } void StreamServer::onResync(const PcmStream* pcmStream, double ms) { - LOG(INFO) << "onResync (" << pcmStream->getName() << "): " << ms << "ms\n"; + LOG(INFO) << "onResync (" << pcmStream->getName() << "): " << ms << "ms\n"; } void StreamServer::onDisconnect(StreamSession* streamSession) { - std::lock_guard mlock(sessionsMutex_); - session_ptr session = getStreamSession(streamSession); + std::lock_guard mlock(sessionsMutex_); + session_ptr session = getStreamSession(streamSession); - if (session == nullptr) - return; + if (session == nullptr) + return; - LOG(INFO) << "onDisconnect: " << session->clientId << "\n"; - LOG(DEBUG) << "sessions: " << sessions_.size() << "\n"; - // don't block: remove StreamSession in a thread - auto func = [](shared_ptr s)->void{s->stop();}; - std::thread t(func, session); - t.detach(); - sessions_.erase(session); + LOG(INFO) << "onDisconnect: " << session->clientId << "\n"; + LOG(DEBUG) << "sessions: " << sessions_.size() << "\n"; + // don't block: remove StreamSession in a thread + auto func = [](shared_ptr s) -> void { s->stop(); }; + std::thread t(func, session); + t.detach(); + sessions_.erase(session); - LOG(DEBUG) << "sessions: " << sessions_.size() << "\n"; + LOG(DEBUG) << "sessions: " << sessions_.size() << "\n"; - // notify controllers if not yet done - ClientInfoPtr clientInfo = Config::instance().getClientInfo(session->clientId); - if (!clientInfo || !clientInfo->connected) - return; + // notify controllers if not yet done + ClientInfoPtr clientInfo = Config::instance().getClientInfo(session->clientId); + if (!clientInfo || !clientInfo->connected) + return; - clientInfo->connected = false; - chronos::systemtimeofday(&clientInfo->lastSeen); - Config::instance().save(); - if (controlServer_ != nullptr) - { - /// Check if there is no session of this client is left - /// Can happen in case of ungraceful disconnect/reconnect or - /// in case of a duplicate client id - 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"}} - json notification = jsonrpcpp::Notification("Client.OnDisconnect", jsonrpcpp::Parameter("id", clientInfo->id, "client", clientInfo->toJson())).to_json(); - controlServer_->send(notification.dump()); - ////cout << "Notification: " << notification.dump() << "\n"; - } - } + clientInfo->connected = false; + chronos::systemtimeofday(&clientInfo->lastSeen); + Config::instance().save(); + if (controlServer_ != nullptr) + { + /// Check if there is no session of this client is left + /// Can happen in case of ungraceful disconnect/reconnect or + /// in case of a duplicate client id + 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"}} + json notification = + jsonrpcpp::Notification("Client.OnDisconnect", jsonrpcpp::Parameter("id", clientInfo->id, "client", clientInfo->toJson())).to_json(); + controlServer_->send(notification.dump()); + ////cout << "Notification: " << notification.dump() << "\n"; + } + } } void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcpp::entity_ptr& response, jsonrpcpp::notification_ptr& notification) const { - try - { - ////LOG(INFO) << "StreamServer::ProcessRequest method: " << request->method << ", " << "id: " << request->id() << "\n"; - Json result; + try + { + ////LOG(INFO) << "StreamServer::ProcessRequest method: " << request->method << ", " << "id: " << request->id() << "\n"; + Json result; - if (request->method().find("Client.") == 0) - { - ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get("id")); - if (clientInfo == nullptr) - throw jsonrpcpp::InternalErrorException("Client not found", request->id()); + if (request->method().find("Client.") == 0) + { + ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get("id")); + if (clientInfo == nullptr) + throw jsonrpcpp::InternalErrorException("Client not found", request->id()); - if (request->method() == "Client.GetStatus") - { - /// 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"}}}} - result["client"] = clientInfo->toJson(); - } - else if (request->method() == "Client.SetVolume") - { - /// Request: {"id":8,"jsonrpc":"2.0","method":"Client.SetVolume","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}} - /// Response: {"id":8,"jsonrpc":"2.0","result":{"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")); - result["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") - { - /// Request: {"id":7,"jsonrpc":"2.0","method":"Client.SetLatency","params":{"id":"00:21:6a:7d:74:fc#2","latency":10}} - /// Response: {"id":7,"jsonrpc":"2.0","result":{"latency":10}} - /// Notification: {"jsonrpc":"2.0","method":"Client.OnLatencyChanged","params":{"id":"00:21:6a:7d:74:fc#2","latency":10}} - int latency = request->params().get("latency"); - if (latency < -10000) - latency = -10000; - else if (latency > settings_.bufferMs) - latency = settings_.bufferMs; - clientInfo->config.latency = latency; //, -10000, settings_.bufferMs); - result["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") - { - /// Request: {"id":6,"jsonrpc":"2.0","method":"Client.SetName","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}} - /// Response: {"id":6,"jsonrpc":"2.0","result":{"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("name"); - result["name"] = clientInfo->config.name; - notification.reset(new jsonrpcpp::Notification("Client.OnNameChanged", jsonrpcpp::Parameter("id", clientInfo->id, "name", clientInfo->config.name))); - } - else - throw jsonrpcpp::MethodNotFoundException(request->id()); + if (request->method() == "Client.GetStatus") + { + /// 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"}}}} + result["client"] = clientInfo->toJson(); + } + else if (request->method() == "Client.SetVolume") + { + /// Request: {"id":8,"jsonrpc":"2.0","method":"Client.SetVolume","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}} + /// Response: {"id":8,"jsonrpc":"2.0","result":{"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")); + result["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") + { + /// Request: {"id":7,"jsonrpc":"2.0","method":"Client.SetLatency","params":{"id":"00:21:6a:7d:74:fc#2","latency":10}} + /// Response: {"id":7,"jsonrpc":"2.0","result":{"latency":10}} + /// Notification: {"jsonrpc":"2.0","method":"Client.OnLatencyChanged","params":{"id":"00:21:6a:7d:74:fc#2","latency":10}} + int latency = request->params().get("latency"); + if (latency < -10000) + latency = -10000; + else if (latency > settings_.bufferMs) + latency = settings_.bufferMs; + clientInfo->config.latency = latency; //, -10000, settings_.bufferMs); + result["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") + { + /// Request: {"id":6,"jsonrpc":"2.0","method":"Client.SetName","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}} + /// Response: {"id":6,"jsonrpc":"2.0","result":{"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("name"); + result["name"] = clientInfo->config.name; + notification.reset( + new jsonrpcpp::Notification("Client.OnNameChanged", jsonrpcpp::Parameter("id", clientInfo->id, "name", clientInfo->config.name))); + } + else + throw jsonrpcpp::MethodNotFoundException(request->id()); - if (request->method().find("Client.Set") == 0) - { - /// Update client - session_ptr session = getStreamSession(clientInfo->id); - if (session != nullptr) - { - auto serverSettings = make_shared(); - serverSettings->setBufferMs(settings_.bufferMs); - serverSettings->setVolume(clientInfo->config.volume.percent); - GroupPtr group = Config::instance().getGroupFromClient(clientInfo); - serverSettings->setMuted(clientInfo->config.volume.muted || group->muted); - serverSettings->setLatency(clientInfo->config.latency); - session->sendAsync(serverSettings); - } - } - } - else if (request->method().find("Group.") == 0) - { - GroupPtr group = Config::instance().getGroup(request->params().get("id")); - if (group == nullptr) - throw jsonrpcpp::InternalErrorException("Group not found", request->id()); + if (request->method().find("Client.Set") == 0) + { + /// Update client + session_ptr session = getStreamSession(clientInfo->id); + if (session != nullptr) + { + auto serverSettings = make_shared(); + serverSettings->setBufferMs(settings_.bufferMs); + serverSettings->setVolume(clientInfo->config.volume.percent); + GroupPtr group = Config::instance().getGroupFromClient(clientInfo); + serverSettings->setMuted(clientInfo->config.volume.muted || group->muted); + serverSettings->setLatency(clientInfo->config.latency); + session->sendAsync(serverSettings); + } + } + } + else if (request->method().find("Group.") == 0) + { + GroupPtr group = Config::instance().getGroup(request->params().get("id")); + if (group == nullptr) + throw jsonrpcpp::InternalErrorException("Group not found", request->id()); - if (request->method() == "Group.GetStatus") - { - /// 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"}}} - result["group"] = group->toJson(); - } - else if (request->method() == "Group.SetMute") - { - /// Request: {"id":5,"jsonrpc":"2.0","method":"Group.SetMute","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","mute":true}} - /// Response: {"id":5,"jsonrpc":"2.0","result":{"mute":true}} - /// Notification: {"jsonrpc":"2.0","method":"Group.OnMute","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","mute":true}} - bool muted = request->params().get("mute"); - group->muted = muted; + if (request->method() == "Group.GetStatus") + { + /// 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"}}} + result["group"] = group->toJson(); + } + else if (request->method() == "Group.SetMute") + { + /// Request: {"id":5,"jsonrpc":"2.0","method":"Group.SetMute","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","mute":true}} + /// Response: {"id":5,"jsonrpc":"2.0","result":{"mute":true}} + /// Notification: {"jsonrpc":"2.0","method":"Group.OnMute","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","mute":true}} + bool muted = request->params().get("mute"); + group->muted = muted; - /// Update clients - for (auto client: group->clients) - { - session_ptr session = getStreamSession(client->id); - if (session != nullptr) - { - auto serverSettings = make_shared(); - serverSettings->setBufferMs(settings_.bufferMs); - serverSettings->setVolume(client->config.volume.percent); - GroupPtr group = Config::instance().getGroupFromClient(client); - serverSettings->setMuted(client->config.volume.muted || group->muted); - serverSettings->setLatency(client->config.latency); - session->sendAsync(serverSettings); - } - } + /// Update clients + for (auto client : group->clients) + { + session_ptr session = getStreamSession(client->id); + if (session != nullptr) + { + auto serverSettings = make_shared(); + serverSettings->setBufferMs(settings_.bufferMs); + serverSettings->setVolume(client->config.volume.percent); + GroupPtr group = Config::instance().getGroupFromClient(client); + serverSettings->setMuted(client->config.volume.muted || group->muted); + serverSettings->setLatency(client->config.latency); + session->sendAsync(serverSettings); + } + } - result["mute"] = group->muted; - notification.reset(new jsonrpcpp::Notification("Group.OnMute", jsonrpcpp::Parameter("id", group->id, "mute", group->muted))); - } - 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"}} - /// 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"}} - string streamId = request->params().get("stream_id"); - PcmStreamPtr stream = streamManager_->getStream(streamId); - if (stream == nullptr) - throw jsonrpcpp::InternalErrorException("Stream not found", request->id()); + result["mute"] = group->muted; + notification.reset(new jsonrpcpp::Notification("Group.OnMute", jsonrpcpp::Parameter("id", group->id, "mute", group->muted))); + } + 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"}} + /// 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"}} + string streamId = request->params().get("stream_id"); + PcmStreamPtr stream = streamManager_->getStream(streamId); + if (stream == nullptr) + throw jsonrpcpp::InternalErrorException("Stream not found", request->id()); - group->streamId = streamId; + group->streamId = streamId; - /// Update clients - for (auto client: group->clients) - { - session_ptr session = getStreamSession(client->id); - if (session && (session->pcmStream() != stream)) - { - session->sendAsync(stream->getMeta()); - session->sendAsync(stream->getHeader()); - session->setPcmStream(stream); - } - } + /// Update clients + for (auto client : group->clients) + { + session_ptr session = getStreamSession(client->id); + if (session && (session->pcmStream() != stream)) + { + session->sendAsync(stream->getMeta()); + session->sendAsync(stream->getHeader()); + session->setPcmStream(stream); + } + } - /// Notify others - result["stream_id"] = group->streamId; - notification.reset(new jsonrpcpp::Notification("Group.OnStreamChanged", jsonrpcpp::Parameter("id", group->id, "stream_id", group->streamId))); - } - 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"}} - /// 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 clients = request->params().get("clients"); - /// Remove clients from group - for (auto iter = group->clients.begin(); iter != group->clients.end();) - { - auto client = *iter; - if (find(clients.begin(), clients.end(), client->id) != clients.end()) - { - ++iter; - continue; - } - iter = group->clients.erase(iter); - GroupPtr newGroup = Config::instance().addClientInfo(client); - newGroup->streamId = group->streamId; - } + /// Notify others + result["stream_id"] = group->streamId; + notification.reset(new jsonrpcpp::Notification("Group.OnStreamChanged", jsonrpcpp::Parameter("id", group->id, "stream_id", group->streamId))); + } + 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"}} + /// 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 clients = request->params().get("clients"); + /// Remove clients from group + for (auto iter = group->clients.begin(); iter != group->clients.end();) + { + auto client = *iter; + if (find(clients.begin(), clients.end(), client->id) != clients.end()) + { + ++iter; + continue; + } + iter = group->clients.erase(iter); + GroupPtr newGroup = Config::instance().addClientInfo(client); + newGroup->streamId = group->streamId; + } - /// Add clients to group - PcmStreamPtr stream = streamManager_->getStream(group->streamId); - for (const auto& clientId: clients) - { - ClientInfoPtr client = Config::instance().getClientInfo(clientId); - if (!client) - continue; - GroupPtr oldGroup = Config::instance().getGroupFromClient(client); - if (oldGroup && (oldGroup->id == group->id)) - continue; + /// Add clients to group + PcmStreamPtr stream = streamManager_->getStream(group->streamId); + for (const auto& clientId : clients) + { + ClientInfoPtr client = Config::instance().getClientInfo(clientId); + if (!client) + continue; + GroupPtr oldGroup = Config::instance().getGroupFromClient(client); + if (oldGroup && (oldGroup->id == group->id)) + continue; - if (oldGroup) - { - oldGroup->removeClient(client); - Config::instance().remove(oldGroup); - } + if (oldGroup) + { + oldGroup->removeClient(client); + Config::instance().remove(oldGroup); + } - group->addClient(client); + group->addClient(client); - /// assign new stream - session_ptr session = getStreamSession(client->id); - if (session && stream && (session->pcmStream() != stream)) - { - session->sendAsync(stream->getMeta()); - session->sendAsync(stream->getHeader()); - session->setPcmStream(stream); - } - } + /// assign new stream + session_ptr session = getStreamSession(client->id); + if (session && stream && (session->pcmStream() != stream)) + { + session->sendAsync(stream->getMeta()); + session->sendAsync(stream->getHeader()); + session->setPcmStream(stream); + } + } - if (group->empty()) - Config::instance().remove(group); + if (group->empty()) + Config::instance().remove(group); - json server = Config::instance().getServerStatus(streamManager_->toJson()); - result["server"] = server; + json server = Config::instance().getServerStatus(streamManager_->toJson()); + result["server"] = server; - /// Notify others: since at least two groups are affected, send a complete server update - notification.reset(new jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server))); - } - else - throw jsonrpcpp::MethodNotFoundException(request->id()); - } - else if (request->method().find("Server.") == 0) - { - if (request->method().find("Server.GetRPCVersion") == 0) - { - /// Request: {"id":8,"jsonrpc":"2.0","method":"Server.GetRPCVersion"} - /// Response: {"id":8,"jsonrpc":"2.0","result":{"major":2,"minor":0,"patch":0}} - // : backwards incompatible change - result["major"] = 2; - // : feature addition to the API - result["minor"] = 0; - // : bugfix release - result["patch"] = 0; - } - else if (request->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"}}]}}} - result["server"] = Config::instance().getServerStatus(streamManager_->toJson()); - } - else if (request->method() == "Server.DeleteClient") - { - /// 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"}}]}}} - /// 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("id")); - if (clientInfo == nullptr) - throw jsonrpcpp::InternalErrorException("Client not found", request->id()); + /// Notify others: since at least two groups are affected, send a complete server update + notification.reset(new jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server))); + } + else + throw jsonrpcpp::MethodNotFoundException(request->id()); + } + else if (request->method().find("Server.") == 0) + { + if (request->method().find("Server.GetRPCVersion") == 0) + { + /// Request: {"id":8,"jsonrpc":"2.0","method":"Server.GetRPCVersion"} + /// Response: {"id":8,"jsonrpc":"2.0","result":{"major":2,"minor":0,"patch":0}} + // : backwards incompatible change + result["major"] = 2; + // : feature addition to the API + result["minor"] = 0; + // : bugfix release + result["patch"] = 0; + } + else if (request->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"}}]}}} + result["server"] = Config::instance().getServerStatus(streamManager_->toJson()); + } + else if (request->method() == "Server.DeleteClient") + { + /// 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"}}]}}} + /// 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("id")); + if (clientInfo == nullptr) + throw jsonrpcpp::InternalErrorException("Client not found", request->id()); - Config::instance().remove(clientInfo); + Config::instance().remove(clientInfo); - json server = Config::instance().getServerStatus(streamManager_->toJson()); - result["server"] = server; + json server = Config::instance().getServerStatus(streamManager_->toJson()); + result["server"] = server; - /// Notify others - notification.reset(new jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server))); - } - else - throw jsonrpcpp::MethodNotFoundException(request->id()); - } - else if (request->method().find("Stream.") == 0) - { - if (request->method().find("Stream.SetMeta") == 0) - { - /// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.SetMeta","params":{"id":"Spotify", - /// "meta": {"album": "some album", "artist": "some artist", "track": "some track"...}}} - /// - /// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"Spotify"}} - /// Call onMetaChanged(const PcmStream* pcmStream) for updates and notifications + /// Notify others + notification.reset(new jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server))); + } + else + throw jsonrpcpp::MethodNotFoundException(request->id()); + } + else if (request->method().find("Stream.") == 0) + { + if (request->method().find("Stream.SetMeta") == 0) + { + /// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.SetMeta","params":{"id":"Spotify", + /// "meta": {"album": "some album", "artist": "some artist", "track": "some track"...}}} + /// + /// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"Spotify"}} + /// Call onMetaChanged(const PcmStream* pcmStream) for updates and notifications - LOG(INFO) << "Stream.SetMeta(" << request->params().get("id") << ")" << request->params().get("meta") <<"\n"; + LOG(INFO) << "Stream.SetMeta(" << request->params().get("id") << ")" << request->params().get("meta") << "\n"; - // Find stream - string streamId = request->params().get("id"); - PcmStreamPtr stream = streamManager_->getStream(streamId); - if (stream == nullptr) - throw jsonrpcpp::InternalErrorException("Stream not found", request->id()); + // Find stream + string streamId = request->params().get("id"); + PcmStreamPtr stream = streamManager_->getStream(streamId); + if (stream == nullptr) + throw jsonrpcpp::InternalErrorException("Stream not found", request->id()); - // Set metadata from request - stream->setMeta(request->params().get("meta")); + // Set metadata from request + stream->setMeta(request->params().get("meta")); - // Setup response - result["id"] = streamId; - } - else - throw jsonrpcpp::MethodNotFoundException(request->id()); - } - else - throw jsonrpcpp::MethodNotFoundException(request->id()); + // Setup response + result["id"] = streamId; + } + else + throw jsonrpcpp::MethodNotFoundException(request->id()); + } + else + throw jsonrpcpp::MethodNotFoundException(request->id()); - Config::instance().save(); - response.reset(new jsonrpcpp::Response(*request, result)); - } - catch (const jsonrpcpp::RequestException& e) - { - LOG(ERROR) << "StreamServer::onMessageReceived JsonRequestException: " << e.to_json().dump() << ", message: " << request->to_json().dump() << "\n"; - response.reset(new jsonrpcpp::RequestException(e)); - } - catch (const exception& e) - { - LOG(ERROR) << "StreamServer::onMessageReceived exception: " << e.what() << ", message: " << request->to_json().dump() << "\n"; - response.reset(new jsonrpcpp::InternalErrorException(e.what(), request->id())); - } + Config::instance().save(); + response.reset(new jsonrpcpp::Response(*request, result)); + } + catch (const jsonrpcpp::RequestException& e) + { + LOG(ERROR) << "StreamServer::onMessageReceived JsonRequestException: " << e.to_json().dump() << ", message: " << request->to_json().dump() << "\n"; + response.reset(new jsonrpcpp::RequestException(e)); + } + catch (const exception& e) + { + LOG(ERROR) << "StreamServer::onMessageReceived exception: " << e.what() << ", message: " << request->to_json().dump() << "\n"; + response.reset(new jsonrpcpp::InternalErrorException(e.what(), request->id())); + } } void StreamServer::onMessageReceived(ControlSession* controlSession, const std::string& message) { - LOG(DEBUG) << "onMessageReceived: " << message << "\n"; - jsonrpcpp::entity_ptr entity(nullptr); - try - { - entity = jsonrpcpp::Parser::do_parse(message); - if (!entity) - return; - } - catch(const jsonrpcpp::ParseErrorException& e) - { - controlSession->send(e.to_json().dump()); - return; - } - catch(const std::exception& e) - { - controlSession->send(jsonrpcpp::ParseErrorException(e.what()).to_json().dump()); - return; - } + LOG(DEBUG) << "onMessageReceived: " << message << "\n"; + jsonrpcpp::entity_ptr entity(nullptr); + try + { + entity = jsonrpcpp::Parser::do_parse(message); + if (!entity) + return; + } + catch (const jsonrpcpp::ParseErrorException& e) + { + controlSession->send(e.to_json().dump()); + return; + } + catch (const std::exception& e) + { + controlSession->send(jsonrpcpp::ParseErrorException(e.what()).to_json().dump()); + return; + } - jsonrpcpp::entity_ptr response(nullptr); - jsonrpcpp::notification_ptr notification(nullptr); - if (entity->is_request()) - { - jsonrpcpp::request_ptr request = dynamic_pointer_cast(entity); - ProcessRequest(request, response, notification); - ////cout << "Request: " << request->to_json().dump() << "\n"; - if (response) - { - ////cout << "Response: " << response->to_json().dump() << "\n"; - controlSession->send(response->to_json().dump()); - } - if (notification) - { - ////cout << "Notification: " << notification->to_json().dump() << "\n"; - controlServer_->send(notification->to_json().dump(), controlSession); - } - } - else if (entity->is_batch()) - { - jsonrpcpp::batch_ptr batch = dynamic_pointer_cast(entity); - ////cout << "Batch: " << batch->to_json().dump() << "\n"; - jsonrpcpp::Batch responseBatch; - jsonrpcpp::Batch notificationBatch; - for (const auto& batch_entity: batch->entities) - { - if (batch_entity->is_request()) - { - jsonrpcpp::request_ptr request = dynamic_pointer_cast(batch_entity); - ProcessRequest(request, response, notification); - if (response != nullptr) - responseBatch.add_ptr(response); - if (notification != nullptr) - notificationBatch.add_ptr(notification); - } - } - if (!responseBatch.entities.empty()) - controlSession->send(responseBatch.to_json().dump()); - if (!notificationBatch.entities.empty()) - controlServer_->send(notificationBatch.to_json().dump(), controlSession); - } + jsonrpcpp::entity_ptr response(nullptr); + jsonrpcpp::notification_ptr notification(nullptr); + if (entity->is_request()) + { + jsonrpcpp::request_ptr request = dynamic_pointer_cast(entity); + ProcessRequest(request, response, notification); + ////cout << "Request: " << request->to_json().dump() << "\n"; + if (response) + { + ////cout << "Response: " << response->to_json().dump() << "\n"; + controlSession->send(response->to_json().dump()); + } + if (notification) + { + ////cout << "Notification: " << notification->to_json().dump() << "\n"; + controlServer_->send(notification->to_json().dump(), controlSession); + } + } + else if (entity->is_batch()) + { + jsonrpcpp::batch_ptr batch = dynamic_pointer_cast(entity); + ////cout << "Batch: " << batch->to_json().dump() << "\n"; + jsonrpcpp::Batch responseBatch; + jsonrpcpp::Batch notificationBatch; + for (const auto& batch_entity : batch->entities) + { + if (batch_entity->is_request()) + { + jsonrpcpp::request_ptr request = dynamic_pointer_cast(batch_entity); + ProcessRequest(request, response, notification); + if (response != nullptr) + responseBatch.add_ptr(response); + if (notification != nullptr) + notificationBatch.add_ptr(notification); + } + } + if (!responseBatch.entities.empty()) + controlSession->send(responseBatch.to_json().dump()); + if (!notificationBatch.entities.empty()) + controlServer_->send(notificationBatch.to_json().dump(), controlSession); + } } 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"; - if (baseMessage.type == message_type::kTime) - { - auto timeMsg = make_shared(); - timeMsg->deserialize(baseMessage, buffer); - timeMsg->refersTo = timeMsg->id; - timeMsg->latency = timeMsg->received - timeMsg->sent; -// LOG(INFO) << "Latency sec: " << timeMsg.latency.sec << ", usec: " << timeMsg.latency.usec << ", refers to: " << timeMsg.refersTo << "\n"; - streamSession->sendAsync(timeMsg); + // 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) + { + auto timeMsg = make_shared(); + timeMsg->deserialize(baseMessage, buffer); + timeMsg->refersTo = timeMsg->id; + timeMsg->latency = timeMsg->received - timeMsg->sent; + // LOG(INFO) << "Latency sec: " << timeMsg.latency.sec << ", usec: " << timeMsg.latency.usec << ", refers to: " << timeMsg.refersTo << + //"\n"; + streamSession->sendAsync(timeMsg); - // refresh streamSession state - ClientInfoPtr client = Config::instance().getClientInfo(streamSession->clientId); - if (client != nullptr) - { - chronos::systemtimeofday(&client->lastSeen); - client->connected = true; - } - } - else if (baseMessage.type == message_type::kHello) - { - msg::Hello helloMsg; - helloMsg.deserialize(baseMessage, buffer); - streamSession->clientId = helloMsg.getUniqueId(); - LOG(INFO) << "Hello from " << streamSession->clientId << ", host: " << helloMsg.getHostName() << ", v" << helloMsg.getVersion() - << ", ClientName: " << helloMsg.getClientName() << ", OS: " << helloMsg.getOS() << ", Arch: " << helloMsg.getArch() - << ", Protocol version: " << helloMsg.getProtocolVersion() << "\n"; + // refresh streamSession state + ClientInfoPtr client = Config::instance().getClientInfo(streamSession->clientId); + if (client != nullptr) + { + chronos::systemtimeofday(&client->lastSeen); + client->connected = true; + } + } + else if (baseMessage.type == message_type::kHello) + { + msg::Hello helloMsg; + helloMsg.deserialize(baseMessage, buffer); + streamSession->clientId = helloMsg.getUniqueId(); + LOG(INFO) << "Hello from " << streamSession->clientId << ", host: " << helloMsg.getHostName() << ", v" << helloMsg.getVersion() + << ", ClientName: " << helloMsg.getClientName() << ", OS: " << helloMsg.getOS() << ", Arch: " << helloMsg.getArch() + << ", Protocol version: " << helloMsg.getProtocolVersion() << "\n"; - LOG(DEBUG) << "request kServerSettings: " << streamSession->clientId << "\n"; -// std::lock_guard mlock(mutex_); - bool newGroup(false); - GroupPtr group = Config::instance().getGroupFromClient(streamSession->clientId); - if (group == nullptr) - { - group = Config::instance().addClientInfo(streamSession->clientId); - newGroup = true; - } + LOG(DEBUG) << "request kServerSettings: " << streamSession->clientId << "\n"; + // std::lock_guard mlock(mutex_); + bool newGroup(false); + GroupPtr group = Config::instance().getGroupFromClient(streamSession->clientId); + if (group == nullptr) + { + group = Config::instance().addClientInfo(streamSession->clientId); + newGroup = true; + } - ClientInfoPtr client = group->getClient(streamSession->clientId); + ClientInfoPtr client = group->getClient(streamSession->clientId); - LOG(DEBUG) << "request kServerSettings\n"; - auto serverSettings = make_shared(); - serverSettings->setVolume(client->config.volume.percent); - serverSettings->setMuted(client->config.volume.muted || group->muted); - serverSettings->setLatency(client->config.latency); - serverSettings->setBufferMs(settings_.bufferMs); - serverSettings->refersTo = helloMsg.id; - streamSession->sendAsync(serverSettings); + LOG(DEBUG) << "request kServerSettings\n"; + auto serverSettings = make_shared(); + serverSettings->setVolume(client->config.volume.percent); + serverSettings->setMuted(client->config.volume.muted || group->muted); + serverSettings->setLatency(client->config.latency); + serverSettings->setBufferMs(settings_.bufferMs); + serverSettings->refersTo = helloMsg.id; + streamSession->sendAsync(serverSettings); - client->host.mac = helloMsg.getMacAddress(); - client->host.ip = streamSession->getIP(); - client->host.name = helloMsg.getHostName(); - client->host.os = helloMsg.getOS(); - client->host.arch = helloMsg.getArch(); - client->snapclient.version = helloMsg.getVersion(); - client->snapclient.name = helloMsg.getClientName(); - client->snapclient.protocolVersion = helloMsg.getProtocolVersion(); - client->config.instance = helloMsg.getInstance(); - client->connected = true; - chronos::systemtimeofday(&client->lastSeen); + client->host.mac = helloMsg.getMacAddress(); + client->host.ip = streamSession->getIP(); + client->host.name = helloMsg.getHostName(); + client->host.os = helloMsg.getOS(); + client->host.arch = helloMsg.getArch(); + client->snapclient.version = helloMsg.getVersion(); + client->snapclient.name = helloMsg.getClientName(); + client->snapclient.protocolVersion = helloMsg.getProtocolVersion(); + client->config.instance = helloMsg.getInstance(); + client->connected = true; + chronos::systemtimeofday(&client->lastSeen); - // Assign and update stream - PcmStreamPtr stream = streamManager_->getStream(group->streamId); - if (!stream) - { - stream = streamManager_->getDefaultStream(); - group->streamId = stream->getId(); - } - LOG(DEBUG) << "Group: " << group->id << ", stream: " << group->streamId << "\n"; + // Assign and update stream + PcmStreamPtr stream = streamManager_->getStream(group->streamId); + if (!stream) + { + stream = streamManager_->getDefaultStream(); + group->streamId = stream->getId(); + } + LOG(DEBUG) << "Group: " << group->id << ", stream: " << group->streamId << "\n"; - Config::instance().save(); + Config::instance().save(); - streamSession->sendAsync(stream->getMeta()); - streamSession->setPcmStream(stream); - auto headerChunk = stream->getHeader(); - streamSession->sendAsync(headerChunk); + streamSession->sendAsync(stream->getMeta()); + streamSession->setPcmStream(stream); + auto headerChunk = stream->getHeader(); + streamSession->sendAsync(headerChunk); - 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"}}]}}} - json server = Config::instance().getServerStatus(streamManager_->toJson()); - json notification = jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server)).to_json(); - controlServer_->send(notification.dump()); - ////cout << "Notification: " << notification.dump() << "\n"; - } - 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"}} - json notification = jsonrpcpp::Notification("Client.OnConnect", jsonrpcpp::Parameter("id", client->id, "client", client->toJson())).to_json(); - controlServer_->send(notification.dump()); - ////cout << "Notification: " << notification.dump() << "\n"; - } -// cout << Config::instance().getServerStatus(streamManager_->toJson()).dump(4) << "\n"; -// cout << group->toJson().dump(4) << "\n"; - } + 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"}}]}}} + json server = Config::instance().getServerStatus(streamManager_->toJson()); + json notification = jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server)).to_json(); + controlServer_->send(notification.dump()); + ////cout << "Notification: " << notification.dump() << "\n"; + } + 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"}} + json notification = jsonrpcpp::Notification("Client.OnConnect", jsonrpcpp::Parameter("id", client->id, "client", client->toJson())).to_json(); + controlServer_->send(notification.dump()); + ////cout << "Notification: " << notification.dump() << "\n"; + } + // cout << Config::instance().getServerStatus(streamManager_->toJson()).dump(4) << "\n"; + // cout << group->toJson().dump(4) << "\n"; + } } session_ptr StreamServer::getStreamSession(StreamSession* streamSession) const { - std::lock_guard mlock(sessionsMutex_); - for (auto session: sessions_) - { - if (session.get() == streamSession) - return session; - } - return nullptr; + std::lock_guard mlock(sessionsMutex_); + for (auto session : sessions_) + { + if (session.get() == streamSession) + return session; + } + return nullptr; } session_ptr StreamServer::getStreamSession(const std::string& clientId) const { -// LOG(INFO) << "getStreamSession: " << mac << "\n"; - std::lock_guard mlock(sessionsMutex_); - for (auto session: sessions_) - { - if (session->clientId == clientId) - return session; - } - return nullptr; + // LOG(INFO) << "getStreamSession: " << mac << "\n"; + std::lock_guard mlock(sessionsMutex_); + for (auto session : sessions_) + { + if (session->clientId == clientId) + return session; + } + return nullptr; } void StreamServer::startAccept() { - if (acceptor_v4_) - { - socket_ptr socket_v4 = make_shared(*io_service_); - acceptor_v4_->async_accept(*socket_v4, bind(&StreamServer::handleAccept, this, socket_v4)); - } - if (acceptor_v6_) - { - socket_ptr socket_v6 = make_shared(*io_service_); - acceptor_v6_->async_accept(*socket_v6, bind(&StreamServer::handleAccept, this, socket_v6)); - } + if (acceptor_v4_) + { + socket_ptr socket_v4 = make_shared(*io_service_); + acceptor_v4_->async_accept(*socket_v4, bind(&StreamServer::handleAccept, this, socket_v4)); + } + if (acceptor_v6_) + { + socket_ptr socket_v6 = make_shared(*io_service_); + acceptor_v6_->async_accept(*socket_v6, bind(&StreamServer::handleAccept, this, socket_v6)); + } } void StreamServer::handleAccept(socket_ptr socket) { - try - { - struct timeval tv; - tv.tv_sec = 5; - tv.tv_usec = 0; - setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); - setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + try + { + struct timeval tv; + tv.tv_sec = 5; + tv.tv_usec = 0; + setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - /// experimental: turn on tcp::no_delay - socket->set_option(tcp::no_delay(true)); + /// experimental: turn on tcp::no_delay + socket->set_option(tcp::no_delay(true)); - SLOG(NOTICE) << "StreamServer::NewConnection: " << socket->remote_endpoint().address().to_string() << endl; - shared_ptr session = make_shared(this, socket); + SLOG(NOTICE) << "StreamServer::NewConnection: " << socket->remote_endpoint().address().to_string() << endl; + shared_ptr session = make_shared(this, socket); - session->setBufferMs(settings_.bufferMs); - session->start(); + session->setBufferMs(settings_.bufferMs); + session->start(); - std::lock_guard mlock(sessionsMutex_); - sessions_.insert(session); - } - catch (const std::exception& e) - { - SLOG(ERROR) << "Exception in StreamServer::handleAccept: " << e.what() << endl; - } - startAccept(); + std::lock_guard mlock(sessionsMutex_); + sessions_.insert(session); + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception in StreamServer::handleAccept: " << e.what() << endl; + } + startAccept(); } void StreamServer::start() { - try - { - controlServer_.reset(new ControlServer(io_service_, settings_.controlPort, this)); - controlServer_->start(); + try + { + controlServer_.reset(new ControlServer(io_service_, settings_.controlPort, this)); + controlServer_->start(); - streamManager_.reset(new StreamManager(this, settings_.sampleFormat, settings_.codec, settings_.streamReadMs)); -// throw SnapException("xxx"); - for (const auto& streamUri: settings_.pcmStreams) - { - PcmStreamPtr stream = streamManager_->addStream(streamUri); - if (stream) - LOG(INFO) << "Stream: " << stream->getUri().toJson() << "\n"; - } - streamManager_->start(); + streamManager_.reset(new StreamManager(this, settings_.sampleFormat, settings_.codec, settings_.streamReadMs)); + // throw SnapException("xxx"); + for (const auto& streamUri : settings_.pcmStreams) + { + PcmStreamPtr stream = streamManager_->addStream(streamUri); + if (stream) + LOG(INFO) << "Stream: " << stream->getUri().toJson() << "\n"; + } + streamManager_->start(); - bool is_v6_only(true); - tcp::endpoint endpoint_v6(tcp::v6(), settings_.port); - try - { - acceptor_v6_ = make_shared(*io_service_, endpoint_v6); - error_code ec; - acceptor_v6_->set_option(asio::ip::v6_only(false), ec); - asio::ip::v6_only option; - acceptor_v6_->get_option(option); - is_v6_only = option.value(); - LOG(DEBUG) << "IPv6 only: " << is_v6_only << "\n"; - } - catch (const asio::system_error& e) - { - LOG(ERROR) << "error creating TCP acceptor: " << e.what() << ", code: " << e.code() << "\n"; - } + bool is_v6_only(true); + tcp::endpoint endpoint_v6(tcp::v6(), settings_.port); + try + { + acceptor_v6_ = make_shared(*io_service_, endpoint_v6); + error_code ec; + acceptor_v6_->set_option(asio::ip::v6_only(false), ec); + asio::ip::v6_only option; + acceptor_v6_->get_option(option); + is_v6_only = option.value(); + LOG(DEBUG) << "IPv6 only: " << is_v6_only << "\n"; + } + catch (const asio::system_error& e) + { + LOG(ERROR) << "error creating TCP acceptor: " << e.what() << ", code: " << e.code() << "\n"; + } - if (!acceptor_v6_ || is_v6_only) - { - tcp::endpoint endpoint_v4(tcp::v4(), settings_.port); - try - { - acceptor_v4_ = make_shared(*io_service_, endpoint_v4); - } - catch (const asio::system_error& e) - { - LOG(ERROR) << "error creating TCP acceptor: " << e.what() << ", code: " << e.code() << "\n"; - } - } + if (!acceptor_v6_ || is_v6_only) + { + tcp::endpoint endpoint_v4(tcp::v4(), settings_.port); + try + { + acceptor_v4_ = make_shared(*io_service_, endpoint_v4); + } + catch (const asio::system_error& e) + { + LOG(ERROR) << "error creating TCP acceptor: " << e.what() << ", code: " << e.code() << "\n"; + } + } - startAccept(); - } - catch (const std::exception& e) - { - SLOG(NOTICE) << "StreamServer::start: " << e.what() << endl; - stop(); - throw; - } + startAccept(); + } + catch (const std::exception& e) + { + SLOG(NOTICE) << "StreamServer::start: " << e.what() << endl; + stop(); + throw; + } } void StreamServer::stop() { - if (streamManager_) - { - streamManager_->stop(); - streamManager_ = nullptr; - } + if (streamManager_) + { + streamManager_->stop(); + streamManager_ = nullptr; + } - { - std::lock_guard mlock(sessionsMutex_); - for (auto session: sessions_)//it = sessions_.begin(); it != sessions_.end(); ++it) - { - if (session) - { - session->stop(); - session = nullptr; - } - } - sessions_.clear(); - } + { + std::lock_guard mlock(sessionsMutex_); + for (auto session : sessions_) // it = sessions_.begin(); it != sessions_.end(); ++it) + { + if (session) + { + session->stop(); + session = nullptr; + } + } + sessions_.clear(); + } - if (controlServer_) - { - controlServer_->stop(); - controlServer_ = nullptr; - } + if (controlServer_) + { + controlServer_->stop(); + controlServer_ = nullptr; + } - if (acceptor_v4_) - { - acceptor_v4_->cancel(); - acceptor_v4_ = nullptr; - } - if (acceptor_v6_) - { - acceptor_v6_->cancel(); - acceptor_v6_ = nullptr; - } + if (acceptor_v4_) + { + acceptor_v4_->cancel(); + acceptor_v4_ = nullptr; + } + if (acceptor_v6_) + { + acceptor_v6_->cancel(); + acceptor_v6_ = nullptr; + } } - diff --git a/server/streamServer.h b/server/streamServer.h index ede20ea0..3229ede0 100644 --- a/server/streamServer.h +++ b/server/streamServer.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -20,22 +20,22 @@ #define STREAM_SERVER_H #include -#include -#include #include +#include #include #include -#include +#include +#include -#include "jsonrpcpp.hpp" -#include "streamSession.h" -#include "streamreader/streamManager.h" #include "common/queue.h" #include "common/sampleFormat.h" -#include "message/message.h" -#include "message/codecHeader.h" -#include "message/serverSettings.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; @@ -44,24 +44,18 @@ typedef std::shared_ptr session_ptr; struct StreamServerSettings { - StreamServerSettings() : - port(1704), - controlPort(1705), - codec("flac"), - bufferMs(1000), - sampleFormat("48000:16:2"), - streamReadMs(20), - sendAudioToMutedClients(false) - { - } - size_t port; - size_t controlPort; - std::vector pcmStreams; - std::string codec; - int32_t bufferMs; - std::string sampleFormat; - size_t streamReadMs; - bool sendAudioToMutedClients; + StreamServerSettings() + : port(1704), controlPort(1705), codec("flac"), bufferMs(1000), sampleFormat("48000:16:2"), streamReadMs(20), sendAudioToMutedClients(false) + { + } + size_t port; + size_t controlPort; + std::vector pcmStreams; + std::string codec; + int32_t bufferMs; + std::string sampleFormat; + size_t streamReadMs; + bool sendAudioToMutedClients; }; @@ -75,48 +69,46 @@ struct StreamServerSettings class StreamServer : public MessageReceiver, ControlMessageReceiver, PcmListener { public: - StreamServer(asio::io_service* io_service, const StreamServerSettings& streamServerSettings); - virtual ~StreamServer(); + StreamServer(asio::io_service* io_service, const StreamServerSettings& streamServerSettings); + virtual ~StreamServer(); - void start(); - void stop(); + void start(); + void stop(); - /// Send a message to all connceted clients -// void send(const msg::BaseMessage* message); + /// Send a message to all connceted clients + // void send(const msg::BaseMessage* message); - /// 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 onDisconnect(StreamSession* connection); + /// 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 onDisconnect(StreamSession* connection); - /// Implementation of ControllMessageReceiver::onMessageReceived, called by ControlServer::onMessageReceived - virtual void onMessageReceived(ControlSession* connection, const std::string& message); + /// Implementation of ControllMessageReceiver::onMessageReceived, called by ControlServer::onMessageReceived + virtual void onMessageReceived(ControlSession* connection, const std::string& message); - /// Implementation of PcmListener - virtual void onMetaChanged(const PcmStream* pcmStream); - virtual void onStateChanged(const PcmStream* pcmStream, const ReaderState& state); - virtual void onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration); - virtual void onResync(const PcmStream* pcmStream, double ms); + /// Implementation of PcmListener + virtual void onMetaChanged(const PcmStream* pcmStream); + virtual void onStateChanged(const PcmStream* pcmStream, const ReaderState& state); + virtual void onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration); + virtual void onResync(const PcmStream* pcmStream, double ms); private: - void startAccept(); - void handleAccept(socket_ptr socket); - session_ptr getStreamSession(const std::string& mac) const; - session_ptr getStreamSession(StreamSession* session) const; - void ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcpp::entity_ptr& response, jsonrpcpp::notification_ptr& notification) const; - mutable std::recursive_mutex sessionsMutex_; - std::set sessions_; - asio::io_service* io_service_; - std::shared_ptr acceptor_v4_; - std::shared_ptr acceptor_v6_; + void startAccept(); + void handleAccept(socket_ptr socket); + session_ptr getStreamSession(const std::string& mac) const; + session_ptr getStreamSession(StreamSession* session) const; + void ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcpp::entity_ptr& response, jsonrpcpp::notification_ptr& notification) const; + mutable std::recursive_mutex sessionsMutex_; + std::set sessions_; + asio::io_service* io_service_; + std::shared_ptr acceptor_v4_; + std::shared_ptr acceptor_v6_; - StreamServerSettings settings_; - Queue> messages_; - std::unique_ptr controlServer_; - std::unique_ptr streamManager_; + StreamServerSettings settings_; + Queue> messages_; + std::unique_ptr controlServer_; + std::unique_ptr streamManager_; }; #endif - - diff --git a/server/streamSession.cpp b/server/streamSession.cpp index 0b274786..f8489fbf 100644 --- a/server/streamSession.cpp +++ b/server/streamSession.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -18,246 +18,246 @@ #include "streamSession.h" -#include -#include #include "aixlog.hpp" #include "message/pcmChunk.h" +#include +#include using namespace std; -StreamSession::StreamSession(MessageReceiver* receiver, std::shared_ptr socket) : - active_(false), readerThread_(nullptr), writerThread_(nullptr), messageReceiver_(receiver), pcmStream_(nullptr) +StreamSession::StreamSession(MessageReceiver* receiver, std::shared_ptr socket) + : active_(false), readerThread_(nullptr), writerThread_(nullptr), messageReceiver_(receiver), pcmStream_(nullptr) { - socket_ = socket; + socket_ = socket; } StreamSession::~StreamSession() { - stop(); + stop(); } void StreamSession::setPcmStream(PcmStreamPtr pcmStream) { - pcmStream_ = pcmStream; + pcmStream_ = pcmStream; } const PcmStreamPtr StreamSession::pcmStream() const { - return pcmStream_; + return pcmStream_; } void StreamSession::start() { - { - std::lock_guard activeLock(activeMutex_); - active_ = true; - } - readerThread_.reset(new thread(&StreamSession::reader, this)); - writerThread_.reset(new thread(&StreamSession::writer, this)); + { + std::lock_guard activeLock(activeMutex_); + active_ = true; + } + readerThread_.reset(new thread(&StreamSession::reader, this)); + writerThread_.reset(new thread(&StreamSession::writer, this)); } void StreamSession::stop() { - { - std::lock_guard activeLock(activeMutex_); - if (!active_) - return; + { + std::lock_guard activeLock(activeMutex_); + if (!active_) + return; - active_ = false; - } + active_ = false; + } - try - { - std::error_code ec; - if (socket_) - { - std::lock_guard socketLock(socketMutex_); - socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); - if (ec) LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n"; - socket_->close(ec); - if (ec) LOG(ERROR) << "Error in socket close: " << ec.message() << "\n"; - } - if (readerThread_ && readerThread_->joinable()) - { - LOG(DEBUG) << "StreamSession joining readerThread\n"; - readerThread_->join(); - } - if (writerThread_ && writerThread_->joinable()) - { - LOG(DEBUG) << "StreamSession joining writerThread\n"; - messages_.abort_wait(); - writerThread_->join(); - } - } - catch(...) - { - } + try + { + std::error_code ec; + if (socket_) + { + std::lock_guard socketLock(socketMutex_); + socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec); + if (ec) + LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n"; + socket_->close(ec); + if (ec) + LOG(ERROR) << "Error in socket close: " << ec.message() << "\n"; + } + if (readerThread_ && readerThread_->joinable()) + { + LOG(DEBUG) << "StreamSession joining readerThread\n"; + readerThread_->join(); + } + if (writerThread_ && writerThread_->joinable()) + { + LOG(DEBUG) << "StreamSession joining writerThread\n"; + messages_.abort_wait(); + writerThread_->join(); + } + } + catch (...) + { + } - readerThread_ = nullptr; - writerThread_ = nullptr; - socket_ = nullptr; - LOG(DEBUG) << "StreamSession stopped\n"; + readerThread_ = nullptr; + writerThread_ = nullptr; + socket_ = nullptr; + LOG(DEBUG) << "StreamSession stopped\n"; } void StreamSession::socketRead(void* _to, size_t _bytes) { - size_t read = 0; - do - { - read += socket_->read_some(asio::buffer((char*)_to + read, _bytes - read)); - } - while (active_ && (read < _bytes)); + size_t read = 0; + do + { + read += socket_->read_some(asio::buffer((char*)_to + read, _bytes - read)); + } while (active_ && (read < _bytes)); } void StreamSession::sendAsync(const msg::message_ptr& message, bool sendNow) { - if (!message) - return; + if (!message) + return; - //the writer will take care about old messages - while (messages_.size() > 2000)// chunk->getDuration() > 10000) - messages_.pop(); + // the writer will take care about old messages + while (messages_.size() > 2000) // chunk->getDuration() > 10000) + messages_.pop(); - if (sendNow) - messages_.push_front(message); - else - messages_.push(message); + if (sendNow) + messages_.push_front(message); + else + messages_.push(message); } bool StreamSession::active() const { - return active_; + return active_; } void StreamSession::setBufferMs(size_t bufferMs) { - bufferMs_ = bufferMs; + bufferMs_ = bufferMs; } bool StreamSession::send(const msg::message_ptr& message) const { - //TODO on exception: set active = false -// LOG(INFO) << "send: " << message->type << ", size: " << message->getSize() << ", id: " << message->id << ", refers: " << message->refersTo << "\n"; - std::lock_guard socketLock(socketMutex_); - { - std::lock_guard activeLock(activeMutex_); - if (!socket_ || !active_) - return false; - } - asio::streambuf streambuf; - std::ostream stream(&streambuf); - tv t; - message->sent = t; - message->serialize(stream); - asio::write(*socket_.get(), streambuf); -// LOG(INFO) << "done: " << message->type << ", size: " << message->size << ", id: " << message->id << ", refers: " << message->refersTo << "\n"; - return true; + // TODO on exception: set active = false + // LOG(INFO) << "send: " << message->type << ", size: " << message->getSize() << ", id: " << message->id << ", refers: " << message->refersTo << "\n"; + std::lock_guard socketLock(socketMutex_); + { + std::lock_guard activeLock(activeMutex_); + if (!socket_ || !active_) + return false; + } + asio::streambuf streambuf; + std::ostream stream(&streambuf); + tv t; + message->sent = t; + message->serialize(stream); + asio::write(*socket_.get(), streambuf); + // LOG(INFO) << "done: " << message->type << ", size: " << message->size << ", id: " << message->id << ", refers: " << message->refersTo << "\n"; + return true; } void StreamSession::getNextMessage() { - msg::BaseMessage baseMessage; - size_t baseMsgSize = baseMessage.getSize(); - vector buffer(baseMsgSize); - socketRead(&buffer[0], baseMsgSize); - baseMessage.deserialize(&buffer[0]); + msg::BaseMessage baseMessage; + size_t baseMsgSize = baseMessage.getSize(); + vector buffer(baseMsgSize); + socketRead(&buffer[0], baseMsgSize); + baseMessage.deserialize(&buffer[0]); - if ((baseMessage.type > message_type::kLast) || (baseMessage.type < message_type::kFirst)) - { - stringstream ss; - ss << "unknown message type received: " << baseMessage.type << ", size: " << baseMessage.size; - throw std::runtime_error(ss.str().c_str()); - } - else if (baseMessage.size > msg::max_size) - { - stringstream ss; - ss << "received message of type " << baseMessage.type << " to large: " << baseMessage.size; - throw std::runtime_error(ss.str().c_str()); - } - -// LOG(INFO) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << "\n"; - if (baseMessage.size > buffer.size()) - buffer.resize(baseMessage.size); -// { -// std::lock_guard socketLock(socketMutex_); - socketRead(&buffer[0], baseMessage.size); -// } - tv t; - baseMessage.received = t; + if ((baseMessage.type > message_type::kLast) || (baseMessage.type < message_type::kFirst)) + { + stringstream ss; + ss << "unknown message type received: " << baseMessage.type << ", size: " << baseMessage.size; + throw std::runtime_error(ss.str().c_str()); + } + else if (baseMessage.size > msg::max_size) + { + stringstream ss; + ss << "received message of type " << baseMessage.type << " to large: " << baseMessage.size; + throw std::runtime_error(ss.str().c_str()); + } - if (active_ && (messageReceiver_ != NULL)) - messageReceiver_->onMessageReceived(this, baseMessage, &buffer[0]); + // LOG(INFO) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << + //baseMessage.refersTo << "\n"; + if (baseMessage.size > buffer.size()) + buffer.resize(baseMessage.size); + // { + // std::lock_guard socketLock(socketMutex_); + socketRead(&buffer[0], baseMessage.size); + // } + tv t; + baseMessage.received = t; + + if (active_ && (messageReceiver_ != NULL)) + messageReceiver_->onMessageReceived(this, baseMessage, &buffer[0]); } void StreamSession::reader() { - try - { - while (active_) - { - getNextMessage(); - } - } - catch (const std::exception& e) - { - SLOG(ERROR) << "Exception in StreamSession::reader(): " << e.what() << endl; - } + try + { + while (active_) + { + getNextMessage(); + } + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception in StreamSession::reader(): " << e.what() << endl; + } - if (active_ && (messageReceiver_ != NULL)) - messageReceiver_->onDisconnect(this); + if (active_ && (messageReceiver_ != NULL)) + messageReceiver_->onDisconnect(this); } void StreamSession::writer() { - try - { - asio::streambuf streambuf; - std::ostream stream(&streambuf); - shared_ptr message; - while (active_) - { - if (messages_.try_pop(message, std::chrono::milliseconds(500))) - { - if (bufferMs_ > 0) - { - const msg::WireChunk* wireChunk = dynamic_cast(message.get()); - if (wireChunk != NULL) - { - chronos::time_point_clk now = chronos::clk::now(); - size_t age = 0; - if (now > wireChunk->start()) - age = std::chrono::duration_cast(now - wireChunk->start()).count(); - //LOG(DEBUG) << "PCM chunk. Age: " << age << ", buffer: " << bufferMs_ << ", age > buffer: " << (age > bufferMs_) << "\n"; - if (age > bufferMs_) - continue; - } - } - send(message); - } - } - } - catch (const std::exception& e) - { - SLOG(ERROR) << "Exception in StreamSession::writer(): " << e.what() << endl; - } + try + { + asio::streambuf streambuf; + std::ostream stream(&streambuf); + shared_ptr message; + while (active_) + { + if (messages_.try_pop(message, std::chrono::milliseconds(500))) + { + if (bufferMs_ > 0) + { + const msg::WireChunk* wireChunk = dynamic_cast(message.get()); + if (wireChunk != NULL) + { + chronos::time_point_clk now = chronos::clk::now(); + size_t age = 0; + if (now > wireChunk->start()) + age = std::chrono::duration_cast(now - wireChunk->start()).count(); + // LOG(DEBUG) << "PCM chunk. Age: " << age << ", buffer: " << bufferMs_ << ", age > buffer: " << (age > bufferMs_) << "\n"; + if (age > bufferMs_) + continue; + } + } + send(message); + } + } + } + catch (const std::exception& e) + { + SLOG(ERROR) << "Exception in StreamSession::writer(): " << e.what() << endl; + } - if (active_ && (messageReceiver_ != NULL)) - messageReceiver_->onDisconnect(this); + if (active_ && (messageReceiver_ != NULL)) + messageReceiver_->onDisconnect(this); } - - diff --git a/server/streamSession.h b/server/streamSession.h index a236a399..9ed74b46 100644 --- a/server/streamSession.h +++ b/server/streamSession.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,17 +19,17 @@ #ifndef STREAM_SESSION_H #define STREAM_SESSION_H +#include "common/queue.h" +#include "message/message.h" +#include "streamreader/streamManager.h" +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include "message/message.h" -#include "common/queue.h" -#include "streamreader/streamManager.h" using asio::ip::tcp; @@ -42,8 +42,8 @@ class StreamSession; class MessageReceiver { public: - virtual void onMessageReceived(StreamSession* connection, const msg::BaseMessage& baseMessage, char* buffer) = 0; - virtual void onDisconnect(StreamSession* connection) = 0; + virtual void onMessageReceived(StreamSession* connection, const msg::BaseMessage& baseMessage, char* buffer) = 0; + virtual void onDisconnect(StreamSession* connection) = 0; }; @@ -56,57 +56,52 @@ public: class StreamSession { public: - /// ctor. Received message from the client are passed to MessageReceiver - StreamSession(MessageReceiver* receiver, std::shared_ptr socket); - ~StreamSession(); - void start(); - void stop(); + /// ctor. Received message from the client are passed to MessageReceiver + StreamSession(MessageReceiver* receiver, std::shared_ptr socket); + ~StreamSession(); + void start(); + void stop(); - /// Sends a message to the client (synchronous) - bool send(const msg::message_ptr& message) const; + /// Sends a message to the client (synchronous) + bool send(const msg::message_ptr& message) const; - /// Sends a message to the client (asynchronous) - void sendAsync(const msg::message_ptr& message, bool sendNow = false); + /// Sends a message to the client (asynchronous) + void sendAsync(const msg::message_ptr& message, bool sendNow = false); - bool active() const; + bool active() const; - /// Max playout latency. No need to send PCM data that is older than bufferMs - void setBufferMs(size_t bufferMs); + /// Max playout latency. No need to send PCM data that is older than bufferMs + void setBufferMs(size_t bufferMs); - std::string clientId; + std::string clientId; - std::string getIP() - { - return socket_->remote_endpoint().address().to_string(); - } + std::string getIP() + { + return socket_->remote_endpoint().address().to_string(); + } - void setPcmStream(PcmStreamPtr pcmStream); - const PcmStreamPtr pcmStream() const; + void setPcmStream(PcmStreamPtr pcmStream); + const PcmStreamPtr pcmStream() const; protected: - void socketRead(void* _to, size_t _bytes); - void getNextMessage(); - void reader(); - void writer(); + void socketRead(void* _to, size_t _bytes); + void getNextMessage(); + void reader(); + void writer(); - mutable std::mutex activeMutex_; - std::atomic active_; + mutable std::mutex activeMutex_; + std::atomic active_; - std::unique_ptr readerThread_; - std::unique_ptr writerThread_; - mutable std::mutex socketMutex_; - std::shared_ptr socket_; - MessageReceiver* messageReceiver_; - Queue> messages_; - size_t bufferMs_; - PcmStreamPtr pcmStream_; + std::unique_ptr readerThread_; + std::unique_ptr writerThread_; + mutable std::mutex socketMutex_; + std::shared_ptr socket_; + MessageReceiver* messageReceiver_; + Queue> messages_; + size_t bufferMs_; + PcmStreamPtr pcmStream_; }; - #endif - - - - diff --git a/server/streamreader/airplayStream.cpp b/server/streamreader/airplayStream.cpp index d49aadd4..38a7de8f 100644 --- a/server/streamreader/airplayStream.cpp +++ b/server/streamreader/airplayStream.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -17,215 +17,224 @@ ***/ #include "airplayStream.h" -#include "common/snapException.h" -#include "common/utils/string_utils.h" -#include "common/utils.h" -#include "base64.h" #include "aixlog.hpp" +#include "base64.h" +#include "common/snapException.h" +#include "common/utils.h" +#include "common/utils/string_utils.h" using namespace std; static string hex2str(string input) { - typedef unsigned char byte; - unsigned long x = strtoul(input.c_str(), 0, 16); - byte a[] = {byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x), 0}; - return string((char *)a); + typedef unsigned char byte; + unsigned long x = strtoul(input.c_str(), 0, 16); + byte a[] = {byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x), 0}; + return string((char*)a); } /* * Expat is used in metadata parsing from Shairport-sync. * Without HAS_EXPAT defined no parsing will occur. * - * This is currently defined in airplayStream.h, prolly should + * This is currently defined in airplayStream.h, prolly should * move to Makefile? */ AirplayStream::AirplayStream(PcmListener* pcmListener, const StreamUri& uri) : ProcessStream(pcmListener, uri), port_(5000) { - logStderr_ = true; + logStderr_ = true; - pipePath_ = "/tmp/shairmeta." + cpt::to_string(getpid()); - //cout << "Pipe [" << pipePath_ << "]\n"; + pipePath_ = "/tmp/shairmeta." + cpt::to_string(getpid()); + // cout << "Pipe [" << pipePath_ << "]\n"; - // XXX: Check if pipe exists, delete or throw error + // XXX: Check if pipe exists, delete or throw error - sampleFormat_ = SampleFormat("44100:16:2"); - uri_.query["sampleformat"] = sampleFormat_.getFormat(); + sampleFormat_ = SampleFormat("44100:16:2"); + uri_.query["sampleformat"] = sampleFormat_.getFormat(); - port_ = cpt::stoul(uri_.getQuery("port", "5000")); + port_ = cpt::stoul(uri_.getQuery("port", "5000")); - string devicename = uri_.getQuery("devicename", "Snapcast"); - params_wo_port_ = "--name=\"" + devicename + "\" --output=stdout"; - params_wo_port_ += " --metadata-pipename " + pipePath_; - params_ = params_wo_port_ + " --port=" + cpt::to_string(port_); + string devicename = uri_.getQuery("devicename", "Snapcast"); + params_wo_port_ = "--name=\"" + devicename + "\" --output=stdout"; + params_wo_port_ += " --metadata-pipename " + pipePath_; + params_ = params_wo_port_ + " --port=" + cpt::to_string(port_); - pipeReaderThread_ = thread(&AirplayStream::pipeReader, this); - pipeReaderThread_.detach(); + pipeReaderThread_ = thread(&AirplayStream::pipeReader, this); + pipeReaderThread_.detach(); } AirplayStream::~AirplayStream() { #ifdef HAS_EXPAT - parse(string("")); - XML_ParserFree(parser_); + parse(string("")); + XML_ParserFree(parser_); #endif } #ifdef HAS_EXPAT 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) - { - XML_ParserFree(parser_); - createParser(); - } - return result; + if ((result = XML_Parse(parser_, line.c_str(), line.length(), false)) == XML_STATUS_ERROR) + { + XML_ParserFree(parser_); + createParser(); + } + return result; } void AirplayStream::createParser() { - parser_ = XML_ParserCreate("UTF-8"); - XML_SetElementHandler(parser_, element_start, element_end); - XML_SetCharacterDataHandler(parser_, data); - XML_SetUserData(parser_, this); + parser_ = XML_ParserCreate("UTF-8"); + XML_SetElementHandler(parser_, element_start, element_end); + XML_SetCharacterDataHandler(parser_, data); + XML_SetUserData(parser_, this); - // Make an outer element to keep parsing going - parse(string("")); + // Make an outer element to keep parsing going + parse(string("")); } void AirplayStream::push() { - string data = entry_->data; - if(entry_->isBase64 && entry_->length > 0) - data = base64_decode(data); + string data = entry_->data; + if (entry_->isBase64 && entry_->length > 0) + data = base64_decode(data); - if(entry_->type == "ssnc" && entry_->code == "mdst") - jtag_ = json(); + if (entry_->type == "ssnc" && entry_->code == "mdst") + jtag_ = json(); - if(entry_->code == "asal") jtag_["ALBUM"] = data; - if(entry_->code == "asar") jtag_["ARTIST"] = data; - if(entry_->code == "minm") jtag_["TITLE"] = data; + if (entry_->code == "asal") + jtag_["ALBUM"] = data; + if (entry_->code == "asar") + jtag_["ARTIST"] = data; + if (entry_->code == "minm") + jtag_["TITLE"] = data; - if(entry_->type == "ssnc" && entry_->code == "mden"){ - //LOG(INFO) << "metadata=" << jtag_.dump(4) << "\n"; - setMeta(jtag_); - } + if (entry_->type == "ssnc" && entry_->code == "mden") + { + // LOG(INFO) << "metadata=" << jtag_.dump(4) << "\n"; + setMeta(jtag_); + } } #endif void AirplayStream::pipeReader() { #ifdef HAS_EXPAT - createParser(); + createParser(); #endif - while(true) - { - ifstream pipe(pipePath_); + while (true) + { + ifstream pipe(pipePath_); - if(pipe){ - string line; + if (pipe) + { + string line; - while(getline(pipe, line)){ + while (getline(pipe, line)) + { #ifdef HAS_EXPAT - parse(line); + parse(line); #endif - } - } + } + } - // Wait a little until we try to open it again - this_thread::sleep_for(chrono::milliseconds(500)); - } + // Wait a little until we try to open it again + this_thread::sleep_for(chrono::milliseconds(500)); + } } void AirplayStream::initExeAndPath(const string& filename) { - path_ = ""; - exe_ = findExe(filename); - if (!fileExists(exe_) || (exe_ == "/")) - { - exe_ = findExe("shairport-sync"); - if (!fileExists(exe_)) - throw SnapException("shairport-sync not found"); - } + path_ = ""; + exe_ = findExe(filename); + if (!fileExists(exe_) || (exe_ == "/")) + { + exe_ = findExe("shairport-sync"); + if (!fileExists(exe_)) + throw SnapException("shairport-sync not found"); + } - if (exe_.find("/") != string::npos) - { - path_ = exe_.substr(0, exe_.find_last_of("/") + 1); - exe_ = exe_.substr(exe_.find_last_of("/") + 1); - } + if (exe_.find("/") != string::npos) + { + path_ = exe_.substr(0, exe_.find_last_of("/") + 1); + exe_ = exe_.substr(exe_.find_last_of("/") + 1); + } } void AirplayStream::onStderrMsg(const char* buffer, size_t n) { - string logmsg = utils::string::trim_copy(string(buffer, n)); - if (logmsg.empty()) - return; - LOG(INFO) << "(" << getName() << ") " << logmsg << "\n"; - if (logmsg.find("Is another Shairport Sync running on this device") != string::npos) - { - LOG(ERROR) << "Seem there is another Shairport Sync runnig on port " << port_ << ", switching to port " << port_ + 1 << "\n"; - ++port_; - params_ = params_wo_port_ + " --port=" + cpt::to_string(port_); - } - else if (logmsg.find("Invalid audio output specified") != string::npos) - { - LOG(ERROR) << "shairport sync compiled without stdout audio backend\n"; - LOG(ERROR) << "build with: \"./configure --with-stdout --with-avahi --with-ssl=openssl --with-metadata\"\n"; - } + string logmsg = utils::string::trim_copy(string(buffer, n)); + if (logmsg.empty()) + return; + LOG(INFO) << "(" << getName() << ") " << logmsg << "\n"; + if (logmsg.find("Is another Shairport Sync running on this device") != string::npos) + { + LOG(ERROR) << "Seem there is another Shairport Sync runnig on port " << port_ << ", switching to port " << port_ + 1 << "\n"; + ++port_; + params_ = params_wo_port_ + " --port=" + cpt::to_string(port_); + } + else if (logmsg.find("Invalid audio output specified") != string::npos) + { + LOG(ERROR) << "shairport sync compiled without stdout audio backend\n"; + LOG(ERROR) << "build with: \"./configure --with-stdout --with-avahi --with-ssl=openssl --with-metadata\"\n"; + } } #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; - string name(element_name); + AirplayStream* self = (AirplayStream*)userdata; + string name(element_name); - self->buf_.assign(""); - if(name == "item") self->entry_.reset(new TageEntry); + self->buf_.assign(""); + if (name == "item") + self->entry_.reset(new TageEntry); - for(int i = 0; attr[i]; i += 2){ - string name(attr[i]); - string value(attr[i+1]); - if(name == "encoding") - self->entry_->isBase64 = (value == "base64"); // Quick & dirty.. - } + for (int i = 0; attr[i]; i += 2) + { + string name(attr[i]); + string value(attr[i + 1]); + if (name == "encoding") + 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; - string name(element_name); + AirplayStream* self = (AirplayStream*)userdata; + string name(element_name); - if(name == "code") - self->entry_->code.assign(hex2str(self->buf_)); + if (name == "code") + self->entry_->code.assign(hex2str(self->buf_)); - else if(name == "type") - self->entry_->type.assign(hex2str(self->buf_)); + else if (name == "type") + self->entry_->type.assign(hex2str(self->buf_)); - else if(name == "length") - self->entry_->length = strtoul(self->buf_.c_str(), 0, 10); + else if (name == "length") + self->entry_->length = strtoul(self->buf_.c_str(), 0, 10); - else if(name == "data") - self->entry_->data = self->buf_; + else if (name == "data") + self->entry_->data = self->buf_; - else if(name == "item") - self->push(); + else if (name == "item") + self->push(); - else if(name == "metatags") ; - else cout << "Unknown tag <" << name << ">\n"; + else if (name == "metatags") + ; + 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; - string value(content, (size_t)length); - self->buf_.append(value); + AirplayStream* self = (AirplayStream*)userdata; + string value(content, (size_t)length); + self->buf_.append(value); } #endif - diff --git a/server/streamreader/airplayStream.h b/server/streamreader/airplayStream.h index 3137d2b4..d8a6ae68 100644 --- a/server/streamreader/airplayStream.h +++ b/server/streamreader/airplayStream.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -32,13 +32,15 @@ class TageEntry { public: - TageEntry(): isBase64(false), length(0) {} + TageEntry() : isBase64(false), length(0) + { + } - std::string code; - std::string type; - std::string data; - bool isBase64; - int length; + std::string code; + std::string type; + std::string data; + bool isBase64; + int length; }; /// Starts shairport-sync and reads PCM data from stdout @@ -53,36 +55,36 @@ public: class AirplayStream : public ProcessStream { public: - /// ctor. Encoded PCM data is passed to the PipeListener - AirplayStream(PcmListener* pcmListener, const StreamUri& uri); - virtual ~AirplayStream(); + /// ctor. Encoded PCM data is passed to the PipeListener + AirplayStream(PcmListener* pcmListener, const StreamUri& uri); + virtual ~AirplayStream(); protected: #ifdef HAS_EXPAT - XML_Parser parser_; + XML_Parser parser_; #endif - std::unique_ptr entry_; - std::string buf_; - json jtag_; + std::unique_ptr entry_; + std::string buf_; + json jtag_; - void pipeReader(); + void pipeReader(); #ifdef HAS_EXPAT - int parse(std::string line); - void createParser(); - void push(); + int parse(std::string line); + void createParser(); + void push(); #endif - virtual void onStderrMsg(const char* buffer, size_t n); - virtual void initExeAndPath(const std::string& filename); - size_t port_; - std::string pipePath_; - std::string params_wo_port_; - std::thread pipeReaderThread_; + virtual void onStderrMsg(const char* buffer, size_t n); + virtual void initExeAndPath(const std::string& filename); + size_t port_; + std::string pipePath_; + std::string params_wo_port_; + std::thread pipeReaderThread_; #ifdef HAS_EXPAT - 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 data(void *userdata, const char *content, int length); + 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 data(void* userdata, const char* content, int length); #endif }; diff --git a/server/streamreader/base64.cpp b/server/streamreader/base64.cpp index c8b4ba0d..6647f32c 100644 --- a/server/streamreader/base64.cpp +++ b/server/streamreader/base64.cpp @@ -1,4 +1,4 @@ -/* +/* base64.cpp and base64.h Copyright (C) 2004-2008 René Nyffenegger @@ -26,96 +26,102 @@ */ #include "base64.h" -static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; +static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; -static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); +static inline bool is_base64(unsigned char c) +{ + return (isalnum(c) || (c == '+') || (c == '/')); } -std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) +{ + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - 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[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; + while (in_len--) + { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) + { + 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[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; - for(i = 0; (i <4) ; i++) - ret += base64_chars[char_array_4[i]]; - i = 0; + for (i = 0; (i < 4); i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } } - } - if (i) - { - for(j = i; j < 3; j++) - char_array_3[j] = '\0'; + if (i) + { + for (j = i; j < 3; j++) + char_array_3[j] = '\0'; - 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[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[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[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; - for (j = 0; (j < i + 1); j++) - ret += base64_chars[char_array_4[j]]; + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; - while((i++ < 3)) - ret += '='; - - } - - return ret; - -} -std::string base64_decode(std::string const& encoded_string) { - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - - while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; in_++; - if (i ==4) { - for (i = 0; i <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[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]; - - for (i = 0; (i < 3); i++) - ret += char_array_3[i]; - i = 0; + while ((i++ < 3)) + ret += '='; } - } - if (i) { - for (j = i; j <4; j++) - char_array_4[j] = 0; - - for (j = 0; j <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[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]; - - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } - - return ret; + return ret; } +std::string base64_decode(std::string const& encoded_string) +{ + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) + { + char_array_4[i++] = encoded_string[in_]; + in_++; + if (i == 4) + { + for (i = 0; i < 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[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]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) + { + for (j = i; j < 4; j++) + char_array_4[j] = 0; + + for (j = 0; j < 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[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]; + + for (j = 0; (j < i - 1); j++) + ret += char_array_3[j]; + } + + return ret; +} diff --git a/server/streamreader/base64.h b/server/streamreader/base64.h index 4ebf89d7..f453c441 100644 --- a/server/streamreader/base64.h +++ b/server/streamreader/base64.h @@ -22,4 +22,4 @@ std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len); std::string base64_decode(std::string const& encoded_string); -#endif +#endif diff --git a/server/streamreader/fileStream.cpp b/server/streamreader/fileStream.cpp index 32f0c390..b9b1a704 100644 --- a/server/streamreader/fileStream.cpp +++ b/server/streamreader/fileStream.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,97 +16,97 @@ along with this program. If not, see . ***/ +#include #include #include -#include -#include "fileStream.h" -#include "encoder/encoderFactory.h" #include "aixlog.hpp" #include "common/snapException.h" +#include "encoder/encoderFactory.h" +#include "fileStream.h" using namespace std; - FileStream::FileStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri) { - ifs.open(uri_.path.c_str(), std::ifstream::in|std::ifstream::binary); - if (!ifs.good()) - { - LOG(ERROR) << "failed to open PCM file: \"" + uri_.path + "\"\n"; - throw SnapException("failed to open PCM file: \"" + uri_.path + "\""); - } + ifs.open(uri_.path.c_str(), std::ifstream::in | std::ifstream::binary); + if (!ifs.good()) + { + LOG(ERROR) << "failed to open PCM file: \"" + uri_.path + "\"\n"; + throw SnapException("failed to open PCM file: \"" + uri_.path + "\""); + } } FileStream::~FileStream() { - ifs.close(); + ifs.close(); } void FileStream::worker() { - timeval tvChunk; - std::unique_ptr chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_)); + timeval tvChunk; + std::unique_ptr chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_)); - ifs.seekg (0, ifs.end); - size_t length = ifs.tellg(); - ifs.seekg (0, ifs.beg); + ifs.seekg(0, ifs.end); + size_t length = ifs.tellg(); + ifs.seekg(0, ifs.beg); - setState(kPlaying); + setState(kPlaying); - while (active_) - { - chronos::systemtimeofday(&tvChunk); - tvEncodedChunk_ = tvChunk; - long nextTick = chronos::getTickCount(); - try - { - while (active_) - { - chunk->timestamp.sec = tvChunk.tv_sec; - chunk->timestamp.usec = tvChunk.tv_usec; - size_t toRead = chunk->payloadSize; - size_t count = 0; + while (active_) + { + chronos::systemtimeofday(&tvChunk); + tvEncodedChunk_ = tvChunk; + long nextTick = chronos::getTickCount(); + try + { + while (active_) + { + chunk->timestamp.sec = tvChunk.tv_sec; + chunk->timestamp.usec = tvChunk.tv_usec; + size_t toRead = chunk->payloadSize; + size_t count = 0; - size_t pos = ifs.tellg(); - size_t left = length - pos; - if (left < toRead) - { - ifs.read(chunk->payload, left); - ifs.seekg (0, ifs.beg); - count = left; - } - ifs.read(chunk->payload + count, toRead - count); + size_t pos = ifs.tellg(); + size_t left = length - pos; + if (left < toRead) + { + ifs.read(chunk->payload, left); + ifs.seekg(0, ifs.beg); + count = left; + } + ifs.read(chunk->payload + count, toRead - count); - encoder_->encode(chunk.get()); - if (!active_) break; - nextTick += pcmReadMs_; - chronos::addUs(tvChunk, pcmReadMs_ * 1000); - long currentTick = chronos::getTickCount(); + encoder_->encode(chunk.get()); + if (!active_) + break; + nextTick += pcmReadMs_; + chronos::addUs(tvChunk, pcmReadMs_ * 1000); + long currentTick = chronos::getTickCount(); - if (nextTick >= currentTick) - { -// LOG(INFO) << "sleep: " << nextTick - currentTick << "\n"; - if (!sleep(nextTick - currentTick)) - break; - } - else - { - chronos::systemtimeofday(&tvChunk); - tvEncodedChunk_ = tvChunk; - pcmListener_->onResync(this, currentTick - nextTick); - nextTick = currentTick; - } - } - } - catch(const std::exception& e) - { - LOG(ERROR) << "(FileStream) Exception: " << e.what() << std::endl; - } - } + if (nextTick >= currentTick) + { + // LOG(INFO) << "sleep: " << nextTick - currentTick << "\n"; + if (!sleep(nextTick - currentTick)) + break; + } + else + { + chronos::systemtimeofday(&tvChunk); + tvEncodedChunk_ = tvChunk; + pcmListener_->onResync(this, currentTick - nextTick); + nextTick = currentTick; + } + } + } + catch (const std::exception& e) + { + LOG(ERROR) << "(FileStream) Exception: " << e.what() << std::endl; + } + } } diff --git a/server/streamreader/fileStream.h b/server/streamreader/fileStream.h index d5bb9e96..333a50b5 100644 --- a/server/streamreader/fileStream.h +++ b/server/streamreader/fileStream.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -32,13 +32,13 @@ class FileStream : public PcmStream { public: - /// ctor. Encoded PCM data is passed to the PipeListener - FileStream(PcmListener* pcmListener, const StreamUri& uri); - virtual ~FileStream(); + /// ctor. Encoded PCM data is passed to the PipeListener + FileStream(PcmListener* pcmListener, const StreamUri& uri); + virtual ~FileStream(); protected: - virtual void worker(); - std::ifstream ifs; + virtual void worker(); + std::ifstream ifs; }; diff --git a/server/streamreader/pcmStream.cpp b/server/streamreader/pcmStream.cpp index 07b9d477..3e2fd785 100644 --- a/server/streamreader/pcmStream.cpp +++ b/server/streamreader/pcmStream.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,184 +16,180 @@ along with this program. If not, see . ***/ +#include #include #include -#include -#include "encoder/encoderFactory.h" +#include "aixlog.hpp" #include "common/snapException.h" #include "common/strCompat.h" +#include "encoder/encoderFactory.h" #include "pcmStream.h" -#include "aixlog.hpp" using namespace std; -PcmStream::PcmStream(PcmListener* pcmListener, const StreamUri& uri) : - active_(false), pcmListener_(pcmListener), uri_(uri), pcmReadMs_(20), state_(kIdle) +PcmStream::PcmStream(PcmListener* pcmListener, const StreamUri& uri) : active_(false), pcmListener_(pcmListener), uri_(uri), pcmReadMs_(20), state_(kIdle) { - EncoderFactory encoderFactory; - if (uri_.query.find("codec") == uri_.query.end()) - throw SnapException("Stream URI must have a codec"); - encoder_.reset(encoderFactory.createEncoder(uri_.query["codec"])); + EncoderFactory encoderFactory; + if (uri_.query.find("codec") == uri_.query.end()) + throw SnapException("Stream URI must have a codec"); + encoder_.reset(encoderFactory.createEncoder(uri_.query["codec"])); - if (uri_.query.find("name") == uri_.query.end()) - throw SnapException("Stream URI must have a name"); - name_ = uri_.query["name"]; + if (uri_.query.find("name") == uri_.query.end()) + throw SnapException("Stream URI must have a name"); + name_ = uri_.query["name"]; - if (uri_.query.find("sampleformat") == uri_.query.end()) - throw SnapException("Stream URI must have a sampleformat"); - sampleFormat_ = SampleFormat(uri_.query["sampleformat"]); - LOG(INFO) << "PcmStream sampleFormat: " << sampleFormat_.getFormat() << "\n"; + if (uri_.query.find("sampleformat") == uri_.query.end()) + throw SnapException("Stream URI must have a sampleformat"); + sampleFormat_ = SampleFormat(uri_.query["sampleformat"]); + LOG(INFO) << "PcmStream sampleFormat: " << sampleFormat_.getFormat() << "\n"; - if (uri_.query.find("buffer_ms") != uri_.query.end()) - pcmReadMs_ = cpt::stoul(uri_.query["buffer_ms"]); + if (uri_.query.find("buffer_ms") != uri_.query.end()) + pcmReadMs_ = cpt::stoul(uri_.query["buffer_ms"]); - if (uri_.query.find("dryout_ms") != uri_.query.end()) - dryoutMs_ = cpt::stoul(uri_.query["dryout_ms"]); - else - dryoutMs_ = 2000; + if (uri_.query.find("dryout_ms") != uri_.query.end()) + dryoutMs_ = cpt::stoul(uri_.query["dryout_ms"]); + else + dryoutMs_ = 2000; - //meta_.reset(new msg::StreamTags()); - //meta_->msg["stream"] = name_; - setMeta(json()); + // meta_.reset(new msg::StreamTags()); + // meta_->msg["stream"] = name_; + setMeta(json()); } PcmStream::~PcmStream() { - stop(); + stop(); } std::shared_ptr PcmStream::getHeader() { - return encoder_->getHeader(); + return encoder_->getHeader(); } const StreamUri& PcmStream::getUri() const { - return uri_; + return uri_; } const std::string& PcmStream::getName() const { - return name_; + return name_; } const std::string& PcmStream::getId() const { - return getName(); + return getName(); } const SampleFormat& PcmStream::getSampleFormat() const { - return sampleFormat_; + return sampleFormat_; } void PcmStream::start() { - LOG(DEBUG) << "PcmStream start: " << sampleFormat_.getFormat() << "\n"; - encoder_->init(this, sampleFormat_); - active_ = true; - thread_ = thread(&PcmStream::worker, this); + LOG(DEBUG) << "PcmStream start: " << sampleFormat_.getFormat() << "\n"; + encoder_->init(this, sampleFormat_); + active_ = true; + thread_ = thread(&PcmStream::worker, this); } void PcmStream::stop() { - if (!active_ && !thread_.joinable()) - return; - - active_ = false; - cv_.notify_one(); - if (thread_.joinable()) - thread_.join(); + if (!active_ && !thread_.joinable()) + return; + + active_ = false; + cv_.notify_one(); + if (thread_.joinable()) + thread_.join(); } bool PcmStream::sleep(int32_t ms) { - if (ms < 0) - return true; - std::unique_lock lck(mtx_); - return (!cv_.wait_for(lck, std::chrono::milliseconds(ms), [this] { return !active_; })); + if (ms < 0) + return true; + std::unique_lock lck(mtx_); + return (!cv_.wait_for(lck, std::chrono::milliseconds(ms), [this] { return !active_; })); } ReaderState PcmStream::getState() const { - return state_; + return state_; } void PcmStream::setState(const ReaderState& newState) { - if (newState != state_) - { - state_ = newState; - if (pcmListener_) - pcmListener_->onStateChanged(this, newState); - } + if (newState != state_) + { + state_ = newState; + if (pcmListener_) + pcmListener_->onStateChanged(this, newState); + } } void PcmStream::onChunkEncoded(const Encoder* encoder, msg::PcmChunk* chunk, double duration) { -// LOG(INFO) << "onChunkEncoded: " << duration << " us\n"; - if (duration <= 0) - return; + // LOG(INFO) << "onChunkEncoded: " << duration << " us\n"; + if (duration <= 0) + return; - chunk->timestamp.sec = tvEncodedChunk_.tv_sec; - chunk->timestamp.usec = tvEncodedChunk_.tv_usec; - chronos::addUs(tvEncodedChunk_, duration * 1000); - if (pcmListener_) - pcmListener_->onChunkRead(this, chunk, duration); + chunk->timestamp.sec = tvEncodedChunk_.tv_sec; + chunk->timestamp.usec = tvEncodedChunk_.tv_usec; + chronos::addUs(tvEncodedChunk_, duration * 1000); + if (pcmListener_) + pcmListener_->onChunkRead(this, chunk, duration); } json PcmStream::toJson() const { - string state("unknown"); - if (state_ == kIdle) - state = "idle"; - else if (state_ == kPlaying) - state = "playing"; - else if (state_ == kDisabled) - state = "disabled"; + string state("unknown"); + if (state_ == kIdle) + state = "idle"; + else if (state_ == kPlaying) + state = "playing"; + else if (state_ == kDisabled) + state = "disabled"; - json j = { - {"uri", uri_.toJson()}, - {"id", getId()}, - {"status", state}, - }; + json j = { + {"uri", uri_.toJson()}, {"id", getId()}, {"status", state}, + }; - if(meta_) - j["meta"] = meta_->msg; + if (meta_) + j["meta"] = meta_->msg; - return j; + return j; } std::shared_ptr PcmStream::getMeta() const { - return meta_; + return meta_; } void PcmStream::setMeta(json jtag) { - meta_.reset(new msg::StreamTags(jtag)); - meta_->msg["STREAM"] = name_; - LOG(INFO) << "metadata=" << meta_->msg.dump(4) << "\n"; + meta_.reset(new msg::StreamTags(jtag)); + meta_->msg["STREAM"] = name_; + LOG(INFO) << "metadata=" << meta_->msg.dump(4) << "\n"; - // Trigger a stream update - if (pcmListener_) - pcmListener_->onMetaChanged(this); + // Trigger a stream update + if (pcmListener_) + pcmListener_->onMetaChanged(this); } - diff --git a/server/streamreader/pcmStream.h b/server/streamreader/pcmStream.h index 8eb33447..9311f177 100644 --- a/server/streamreader/pcmStream.h +++ b/server/streamreader/pcmStream.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,28 +19,28 @@ #ifndef PCM_STREAM_H #define PCM_STREAM_H -#include -#include -#include -#include -#include -#include -#include "streamUri.h" -#include "encoder/encoder.h" -#include "common/sampleFormat.h" #include "common/json.hpp" +#include "common/sampleFormat.h" +#include "encoder/encoder.h" #include "message/codecHeader.h" #include "message/streamTags.h" +#include "streamUri.h" +#include +#include +#include +#include +#include +#include class PcmStream; enum ReaderState { - kUnknown = 0, - kIdle = 1, - kPlaying = 2, - kDisabled = 3 + kUnknown = 0, + kIdle = 1, + kPlaying = 2, + kDisabled = 3 }; @@ -51,10 +51,10 @@ enum ReaderState class PcmListener { public: - virtual void onMetaChanged(const PcmStream* pcmStream) = 0; - virtual void onStateChanged(const PcmStream* pcmStream, const ReaderState& state) = 0; - virtual void onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration) = 0; - virtual void onResync(const PcmStream* pcmStream, double ms) = 0; + virtual void onMetaChanged(const PcmStream* pcmStream) = 0; + virtual void onStateChanged(const PcmStream* pcmStream, const ReaderState& state) = 0; + virtual void onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration) = 0; + virtual void onResync(const PcmStream* pcmStream, double ms) = 0; }; @@ -67,49 +67,49 @@ public: class PcmStream : public EncoderListener { public: - /// ctor. Encoded PCM data is passed to the PcmListener - PcmStream(PcmListener* pcmListener, const StreamUri& uri); - virtual ~PcmStream(); + /// ctor. Encoded PCM data is passed to the PcmListener + PcmStream(PcmListener* pcmListener, const StreamUri& uri); + virtual ~PcmStream(); - virtual void start(); - virtual void stop(); + virtual void start(); + virtual void stop(); - /// Implementation of EncoderListener::onChunkEncoded - virtual void onChunkEncoded(const Encoder* encoder, msg::PcmChunk* chunk, double duration); - virtual std::shared_ptr getHeader(); + /// Implementation of EncoderListener::onChunkEncoded + virtual void onChunkEncoded(const Encoder* encoder, msg::PcmChunk* chunk, double duration); + virtual std::shared_ptr getHeader(); - virtual const StreamUri& getUri() const; - virtual const std::string& getName() const; - virtual const std::string& getId() const; - virtual const SampleFormat& getSampleFormat() const; + virtual const StreamUri& getUri() const; + virtual const std::string& getName() const; + virtual const std::string& getId() const; + virtual const SampleFormat& getSampleFormat() const; - std::shared_ptr getMeta() const; - void setMeta(json j); + std::shared_ptr getMeta() const; + void setMeta(json j); - virtual ReaderState getState() const; - virtual json toJson() const; + virtual ReaderState getState() const; + virtual json toJson() const; protected: - std::condition_variable cv_; - std::mutex mtx_; - std::thread thread_; - std::atomic active_; + std::condition_variable cv_; + std::mutex mtx_; + std::thread thread_; + std::atomic active_; - virtual void worker() = 0; - virtual bool sleep(int32_t ms); - void setState(const ReaderState& newState); + virtual void worker() = 0; + virtual bool sleep(int32_t ms); + void setState(const ReaderState& newState); - timeval tvEncodedChunk_; - PcmListener* pcmListener_; - StreamUri uri_; - SampleFormat sampleFormat_; - size_t pcmReadMs_; - size_t dryoutMs_; - std::unique_ptr encoder_; - std::string name_; - ReaderState state_; - std::shared_ptr meta_; + timeval tvEncodedChunk_; + PcmListener* pcmListener_; + StreamUri uri_; + SampleFormat sampleFormat_; + size_t pcmReadMs_; + size_t dryoutMs_; + std::unique_ptr encoder_; + std::string name_; + ReaderState state_; + std::shared_ptr meta_; }; diff --git a/server/streamreader/pipeStream.cpp b/server/streamreader/pipeStream.cpp index 3b374c52..6dd5e954 100644 --- a/server/streamreader/pipeStream.cpp +++ b/server/streamreader/pipeStream.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,138 +16,138 @@ along with this program. If not, see . ***/ +#include +#include #include #include -#include #include -#include -#include "pipeStream.h" -#include "encoder/encoderFactory.h" +#include "aixlog.hpp" #include "common/snapException.h" #include "common/strCompat.h" -#include "aixlog.hpp" +#include "encoder/encoderFactory.h" +#include "pipeStream.h" using namespace std; - PipeStream::PipeStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), fd_(-1) { - umask(0); - string mode = uri_.getQuery("mode", "create"); - - LOG(INFO) << "PipeStream mode: " << mode << "\n"; - if ((mode != "read") && (mode != "create")) - throw SnapException("create mode for fifo must be \"read\" or \"create\""); - - if (mode == "create") - { - if ((mkfifo(uri_.path.c_str(), 0666) != 0) && (errno != EEXIST)) - throw SnapException("failed to make fifo \"" + uri_.path + "\": " + cpt::to_string(errno)); - } + umask(0); + string mode = uri_.getQuery("mode", "create"); + + LOG(INFO) << "PipeStream mode: " << mode << "\n"; + if ((mode != "read") && (mode != "create")) + throw SnapException("create mode for fifo must be \"read\" or \"create\""); + + if (mode == "create") + { + if ((mkfifo(uri_.path.c_str(), 0666) != 0) && (errno != EEXIST)) + throw SnapException("failed to make fifo \"" + uri_.path + "\": " + cpt::to_string(errno)); + } } PipeStream::~PipeStream() { - if (fd_ != -1) - close(fd_); + if (fd_ != -1) + close(fd_); } void PipeStream::worker() { - timeval tvChunk; - std::unique_ptr chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_)); - string lastException = ""; + timeval tvChunk; + std::unique_ptr chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_)); + string lastException = ""; - while (active_) - { - if (fd_ != -1) - close(fd_); - fd_ = open(uri_.path.c_str(), O_RDONLY | O_NONBLOCK); - chronos::systemtimeofday(&tvChunk); - tvEncodedChunk_ = tvChunk; - long nextTick = chronos::getTickCount(); - int idleBytes = 0; - int maxIdleBytes = sampleFormat_.rate*sampleFormat_.frameSize*dryoutMs_/1000; - try - { - if (fd_ == -1) - throw SnapException("failed to open fifo: \"" + uri_.path + "\""); + while (active_) + { + if (fd_ != -1) + close(fd_); + fd_ = open(uri_.path.c_str(), O_RDONLY | O_NONBLOCK); + chronos::systemtimeofday(&tvChunk); + tvEncodedChunk_ = tvChunk; + long nextTick = chronos::getTickCount(); + int idleBytes = 0; + int maxIdleBytes = sampleFormat_.rate * sampleFormat_.frameSize * dryoutMs_ / 1000; + try + { + if (fd_ == -1) + throw SnapException("failed to open fifo: \"" + uri_.path + "\""); - while (active_) - { - chunk->timestamp.sec = tvChunk.tv_sec; - chunk->timestamp.usec = tvChunk.tv_usec; - int toRead = chunk->payloadSize; - int len = 0; - do - { - int count = read(fd_, chunk->payload + len, toRead - len); - if (count < 0 && idleBytes < maxIdleBytes) - { - memset(chunk->payload + len, 0, toRead - len); - idleBytes += toRead - len; - len += toRead - len; - continue; - } - if (count < 0) - { - setState(kIdle); - if (!sleep(100)) - break; - } - else if (count == 0) - throw SnapException("end of file"); - else - { - len += count; - idleBytes = 0; - } - } - while ((len < toRead) && active_); + while (active_) + { + chunk->timestamp.sec = tvChunk.tv_sec; + chunk->timestamp.usec = tvChunk.tv_usec; + int toRead = chunk->payloadSize; + int len = 0; + do + { + int count = read(fd_, chunk->payload + len, toRead - len); + if (count < 0 && idleBytes < maxIdleBytes) + { + memset(chunk->payload + len, 0, toRead - len); + idleBytes += toRead - len; + len += toRead - len; + continue; + } + if (count < 0) + { + setState(kIdle); + if (!sleep(100)) + break; + } + else if (count == 0) + throw SnapException("end of file"); + else + { + len += count; + idleBytes = 0; + } + } while ((len < toRead) && active_); - if (!active_) break; + if (!active_) + break; - /// TODO: use less raw pointers, make this encoding more transparent - encoder_->encode(chunk.get()); + /// TODO: use less raw pointers, make this encoding more transparent + encoder_->encode(chunk.get()); - if (!active_) break; + if (!active_) + break; - nextTick += pcmReadMs_; - chronos::addUs(tvChunk, pcmReadMs_ * 1000); - long currentTick = chronos::getTickCount(); + nextTick += pcmReadMs_; + chronos::addUs(tvChunk, pcmReadMs_ * 1000); + long currentTick = chronos::getTickCount(); - if (nextTick >= currentTick) - { - setState(kPlaying); - if (!sleep(nextTick - currentTick)) - break; - } - else - { - chronos::systemtimeofday(&tvChunk); - tvEncodedChunk_ = tvChunk; - pcmListener_->onResync(this, currentTick - nextTick); - nextTick = currentTick; - } + if (nextTick >= currentTick) + { + setState(kPlaying); + if (!sleep(nextTick - currentTick)) + break; + } + else + { + chronos::systemtimeofday(&tvChunk); + tvEncodedChunk_ = tvChunk; + pcmListener_->onResync(this, currentTick - nextTick); + nextTick = currentTick; + } - lastException = ""; - } - } - catch(const std::exception& e) - { - if (lastException != e.what()) - { - LOG(ERROR) << "(PipeStream) Exception: " << e.what() << std::endl; - lastException = e.what(); - } - if (!sleep(100)) - break; - } - } + lastException = ""; + } + } + catch (const std::exception& e) + { + if (lastException != e.what()) + { + LOG(ERROR) << "(PipeStream) Exception: " << e.what() << std::endl; + lastException = e.what(); + } + if (!sleep(100)) + break; + } + } } diff --git a/server/streamreader/pipeStream.h b/server/streamreader/pipeStream.h index ba6606aa..dafbe82f 100644 --- a/server/streamreader/pipeStream.h +++ b/server/streamreader/pipeStream.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -32,13 +32,13 @@ class PipeStream : public PcmStream { public: - /// ctor. Encoded PCM data is passed to the PipeListener - PipeStream(PcmListener* pcmListener, const StreamUri& uri); - virtual ~PipeStream(); + /// ctor. Encoded PCM data is passed to the PipeListener + PipeStream(PcmListener* pcmListener, const StreamUri& uri); + virtual ~PipeStream(); protected: - virtual void worker(); - int fd_; + virtual void worker(); + int fd_; }; diff --git a/server/streamreader/process.hpp b/server/streamreader/process.hpp index 281996c7..9b283c8a 100644 --- a/server/streamreader/process.hpp +++ b/server/streamreader/process.hpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or diff --git a/server/streamreader/processStream.cpp b/server/streamreader/processStream.cpp index 213ba8ad..9071836c 100644 --- a/server/streamreader/processStream.cpp +++ b/server/streamreader/processStream.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -17,220 +17,220 @@ ***/ -#include -#include -#include #include "processStream.h" -#include "common/snapException.h" -#include "common/utils/string_utils.h" -#include "common/utils.h" #include "aixlog.hpp" +#include "common/snapException.h" +#include "common/utils.h" +#include "common/utils/string_utils.h" +#include +#include +#include using namespace std; - ProcessStream::ProcessStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), path_(""), process_(nullptr) { - params_ = uri_.getQuery("params"); - logStderr_ = (uri_.getQuery("logStderr", "false") == "true"); + params_ = uri_.getQuery("params"); + logStderr_ = (uri_.getQuery("logStderr", "false") == "true"); } ProcessStream::~ProcessStream() { - if (process_) - process_->kill(); + if (process_) + process_->kill(); } bool ProcessStream::fileExists(const std::string& filename) { - struct stat buffer; - return (stat(filename.c_str(), &buffer) == 0); + struct stat buffer; + return (stat(filename.c_str(), &buffer) == 0); } std::string ProcessStream::findExe(const std::string& filename) { - /// check if filename exists - if (fileExists(filename)) - return filename; + /// check if filename exists + if (fileExists(filename)) + return filename; - std::string exe = filename; - if (exe.find("/") != string::npos) - exe = exe.substr(exe.find_last_of("/") + 1); + std::string exe = filename; + if (exe.find("/") != string::npos) + exe = exe.substr(exe.find_last_of("/") + 1); - /// check with "which" - string which = execGetOutput("which " + exe); - if (!which.empty()) - return which; + /// check with "which" + string which = execGetOutput("which " + exe); + if (!which.empty()) + return which; - /// check in the same path as this binary - char buff[PATH_MAX]; - char szTmp[32]; - sprintf(szTmp, "/proc/%d/exe", getpid()); - ssize_t len = readlink(szTmp, buff, sizeof(buff)-1); - if (len != -1) - { - buff[len] = '\0'; - return string(buff) + "/" + exe; - } + /// check in the same path as this binary + char buff[PATH_MAX]; + char szTmp[32]; + sprintf(szTmp, "/proc/%d/exe", getpid()); + ssize_t len = readlink(szTmp, buff, sizeof(buff) - 1); + if (len != -1) + { + buff[len] = '\0'; + return string(buff) + "/" + exe; + } - return ""; + return ""; } void ProcessStream::initExeAndPath(const std::string& filename) { - path_ = ""; - exe_ = findExe(filename); - if (exe_.find("/") != string::npos) - { - path_ = exe_.substr(0, exe_.find_last_of("/") + 1); - exe_ = exe_.substr(exe_.find_last_of("/") + 1); - } + path_ = ""; + exe_ = findExe(filename); + if (exe_.find("/") != string::npos) + { + path_ = exe_.substr(0, exe_.find_last_of("/") + 1); + exe_ = exe_.substr(exe_.find_last_of("/") + 1); + } - if (!fileExists(path_ + exe_)) - throw SnapException("file not found: \"" + filename + "\""); + if (!fileExists(path_ + exe_)) + throw SnapException("file not found: \"" + filename + "\""); } void ProcessStream::start() { - initExeAndPath(uri_.path); - PcmStream::start(); + initExeAndPath(uri_.path); + PcmStream::start(); } void ProcessStream::stop() { - if (process_) - process_->kill(); - PcmStream::stop(); + if (process_) + process_->kill(); + PcmStream::stop(); - /// thread is detached, so it is not joinable - if (stderrReaderThread_.joinable()) - stderrReaderThread_.join(); + /// thread is detached, so it is not joinable + if (stderrReaderThread_.joinable()) + stderrReaderThread_.join(); } void ProcessStream::onStderrMsg(const char* buffer, size_t n) { - if (logStderr_) - { - string line = utils::string::trim_copy(string(buffer, n)); - if ((line.find('\0') == string::npos) && !line.empty()) - LOG(INFO) << "(" << getName() << ") " << line << "\n"; - } + if (logStderr_) + { + string line = utils::string::trim_copy(string(buffer, n)); + if ((line.find('\0') == string::npos) && !line.empty()) + LOG(INFO) << "(" << getName() << ") " << line << "\n"; + } } void ProcessStream::stderrReader() { - size_t buffer_size = 8192; - auto buffer = std::unique_ptr(new char[buffer_size]); - ssize_t n; - stringstream message; - while (active_ && (n=read(process_->getStderr(), buffer.get(), buffer_size)) > 0) - onStderrMsg(buffer.get(), n); + size_t buffer_size = 8192; + auto buffer = std::unique_ptr(new char[buffer_size]); + ssize_t n; + stringstream message; + while (active_ && (n = read(process_->getStderr(), buffer.get(), buffer_size)) > 0) + onStderrMsg(buffer.get(), n); } void ProcessStream::worker() { - timeval tvChunk; - std::unique_ptr chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_)); - setState(kPlaying); - string lastException = ""; + timeval tvChunk; + std::unique_ptr chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_)); + setState(kPlaying); + string lastException = ""; - while (active_) - { - process_.reset(new Process(path_ + exe_ + " " + params_, path_)); - int flags = fcntl(process_->getStdout(), F_GETFL, 0); - fcntl(process_->getStdout(), F_SETFL, flags | O_NONBLOCK); + while (active_) + { + process_.reset(new Process(path_ + exe_ + " " + params_, path_)); + int flags = fcntl(process_->getStdout(), F_GETFL, 0); + fcntl(process_->getStdout(), F_SETFL, flags | O_NONBLOCK); - stderrReaderThread_ = thread(&ProcessStream::stderrReader, this); - stderrReaderThread_.detach(); + stderrReaderThread_ = thread(&ProcessStream::stderrReader, this); + stderrReaderThread_.detach(); - chronos::systemtimeofday(&tvChunk); - tvEncodedChunk_ = tvChunk; - long nextTick = chronos::getTickCount(); - int idleBytes = 0; - int maxIdleBytes = sampleFormat_.rate*sampleFormat_.frameSize*dryoutMs_/1000; - try - { - while (active_) - { - chunk->timestamp.sec = tvChunk.tv_sec; - chunk->timestamp.usec = tvChunk.tv_usec; - int toRead = chunk->payloadSize; - int len = 0; - do - { - int count = read(process_->getStdout(), chunk->payload + len, toRead - len); - if (count < 0 && idleBytes < maxIdleBytes) - { - memset(chunk->payload + len, 0, toRead - len); - idleBytes += toRead - len; - len += toRead - len; - continue; - } - if (count < 0) - { - setState(kIdle); - if (!sleep(100)) - break; - } - else if (count == 0) - throw SnapException("end of file"); - else - { - len += count; - idleBytes = 0; - } - } - while ((len < toRead) && active_); + chronos::systemtimeofday(&tvChunk); + tvEncodedChunk_ = tvChunk; + long nextTick = chronos::getTickCount(); + int idleBytes = 0; + int maxIdleBytes = sampleFormat_.rate * sampleFormat_.frameSize * dryoutMs_ / 1000; + try + { + while (active_) + { + chunk->timestamp.sec = tvChunk.tv_sec; + chunk->timestamp.usec = tvChunk.tv_usec; + int toRead = chunk->payloadSize; + int len = 0; + do + { + int count = read(process_->getStdout(), chunk->payload + len, toRead - len); + if (count < 0 && idleBytes < maxIdleBytes) + { + memset(chunk->payload + len, 0, toRead - len); + idleBytes += toRead - len; + len += toRead - len; + continue; + } + if (count < 0) + { + setState(kIdle); + if (!sleep(100)) + break; + } + else if (count == 0) + throw SnapException("end of file"); + else + { + len += count; + idleBytes = 0; + } + } 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_; - chronos::addUs(tvChunk, pcmReadMs_ * 1000); - long currentTick = chronos::getTickCount(); + nextTick += pcmReadMs_; + chronos::addUs(tvChunk, pcmReadMs_ * 1000); + long currentTick = chronos::getTickCount(); - if (nextTick >= currentTick) - { - setState(kPlaying); - if (!sleep(nextTick - currentTick)) - break; - } - else - { - chronos::systemtimeofday(&tvChunk); - tvEncodedChunk_ = tvChunk; - pcmListener_->onResync(this, currentTick - nextTick); - nextTick = currentTick; - } + if (nextTick >= currentTick) + { + setState(kPlaying); + if (!sleep(nextTick - currentTick)) + break; + } + else + { + chronos::systemtimeofday(&tvChunk); + tvEncodedChunk_ = tvChunk; + pcmListener_->onResync(this, currentTick - nextTick); + nextTick = currentTick; + } - lastException = ""; - } - } - catch(const std::exception& e) - { - if (lastException != e.what()) - { - LOG(ERROR) << "(PipeStream) Exception: " << e.what() << std::endl; - lastException = e.what(); - } - process_->kill(); - if (!sleep(30000)) - break; - } - } + lastException = ""; + } + } + catch (const std::exception& e) + { + if (lastException != e.what()) + { + LOG(ERROR) << "(PipeStream) Exception: " << e.what() << std::endl; + lastException = e.what(); + } + process_->kill(); + if (!sleep(30000)) + break; + } + } } diff --git a/server/streamreader/processStream.h b/server/streamreader/processStream.h index 14b13587..883ccd13 100644 --- a/server/streamreader/processStream.h +++ b/server/streamreader/processStream.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -35,28 +35,28 @@ class ProcessStream : public PcmStream { public: - /// ctor. Encoded PCM data is passed to the PipeListener - ProcessStream(PcmListener* pcmListener, const StreamUri& uri); - virtual ~ProcessStream(); + /// ctor. Encoded PCM data is passed to the PipeListener + ProcessStream(PcmListener* pcmListener, const StreamUri& uri); + virtual ~ProcessStream(); - virtual void start(); - virtual void stop(); + virtual void start(); + virtual void stop(); protected: - std::string exe_; - std::string path_; - std::string params_; - std::unique_ptr process_; - std::thread stderrReaderThread_; - bool logStderr_; + std::string exe_; + std::string path_; + std::string params_; + std::unique_ptr process_; + std::thread stderrReaderThread_; + bool logStderr_; - virtual void worker(); - virtual void stderrReader(); - virtual void onStderrMsg(const char* buffer, size_t n); - virtual void initExeAndPath(const std::string& filename); + virtual void worker(); + virtual void stderrReader(); + virtual void onStderrMsg(const char* buffer, size_t n); + virtual void initExeAndPath(const std::string& filename); - bool fileExists(const std::string& filename); - std::string findExe(const std::string& filename); + bool fileExists(const std::string& filename); + std::string findExe(const std::string& filename); }; diff --git a/server/streamreader/spotifyStream.cpp b/server/streamreader/spotifyStream.cpp index 727b6d6c..4968b679 100644 --- a/server/streamreader/spotifyStream.cpp +++ b/server/streamreader/spotifyStream.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -16,51 +16,50 @@ along with this program. If not, see . ***/ -#include #include "spotifyStream.h" -#include "common/snapException.h" -#include "common/utils/string_utils.h" -#include "common/utils.h" #include "aixlog.hpp" +#include "common/snapException.h" +#include "common/utils.h" +#include "common/utils/string_utils.h" +#include using namespace std; - SpotifyStream::SpotifyStream(PcmListener* pcmListener, const StreamUri& uri) : ProcessStream(pcmListener, uri) { - sampleFormat_ = SampleFormat("44100:16:2"); - uri_.query["sampleformat"] = sampleFormat_.getFormat(); + sampleFormat_ = SampleFormat("44100:16:2"); + uri_.query["sampleformat"] = sampleFormat_.getFormat(); - string username = uri_.getQuery("username", ""); - string password = uri_.getQuery("password", ""); - string cache = uri_.getQuery("cache", ""); - string volume = uri_.getQuery("volume", ""); - string bitrate = uri_.getQuery("bitrate", "320"); - string devicename = uri_.getQuery("devicename", "Snapcast"); - string onevent = uri_.getQuery("onevent", ""); + string username = uri_.getQuery("username", ""); + string password = uri_.getQuery("password", ""); + string cache = uri_.getQuery("cache", ""); + string volume = uri_.getQuery("volume", ""); + string bitrate = uri_.getQuery("bitrate", "320"); + string devicename = uri_.getQuery("devicename", "Snapcast"); + string onevent = uri_.getQuery("onevent", ""); - if (username.empty() != password.empty()) - throw SnapException("missing parameter \"username\" or \"password\" (must provide both, or neither)"); + if (username.empty() != password.empty()) + throw SnapException("missing parameter \"username\" or \"password\" (must provide both, or neither)"); - params_ = "--name \"" + devicename + "\""; - if (!username.empty() && !password.empty()) - params_ += " --username \"" + username + "\" --password \"" + password + "\""; - params_ += " --bitrate " + bitrate + " --backend pipe"; - if (!cache.empty()) - params_ += " --cache \"" + cache + "\""; - if (!volume.empty()) - params_ += " --initial-volume \"" + volume + "\""; - if (!onevent.empty()) - params_ += " --onevent \"" + onevent + "\""; + params_ = "--name \"" + devicename + "\""; + if (!username.empty() && !password.empty()) + params_ += " --username \"" + username + "\" --password \"" + password + "\""; + params_ += " --bitrate " + bitrate + " --backend pipe"; + if (!cache.empty()) + params_ += " --cache \"" + cache + "\""; + if (!volume.empty()) + params_ += " --initial-volume \"" + volume + "\""; + if (!onevent.empty()) + params_ += " --onevent \"" + onevent + "\""; - if (uri_.query.find("username") != uri_.query.end()) - uri_.query["username"] = "xxx"; - if (uri_.query.find("password") != uri_.query.end()) - uri_.query["password"] = "xxx"; -// LOG(INFO) << "params: " << params << "\n"; + if (uri_.query.find("username") != uri_.query.end()) + uri_.query["username"] = "xxx"; + if (uri_.query.find("password") != uri_.query.end()) + uri_.query["password"] = "xxx"; + // LOG(INFO) << "params: " << params << "\n"; } @@ -71,109 +70,103 @@ SpotifyStream::~SpotifyStream() void SpotifyStream::initExeAndPath(const std::string& filename) { - path_ = ""; - exe_ = findExe(filename); - if (!fileExists(exe_) || (exe_ == "/")) - { - exe_ = findExe("librespot"); - if (!fileExists(exe_)) - throw SnapException("librespot not found"); - } + path_ = ""; + exe_ = findExe(filename); + if (!fileExists(exe_) || (exe_ == "/")) + { + exe_ = findExe("librespot"); + if (!fileExists(exe_)) + throw SnapException("librespot not found"); + } - if (exe_.find("/") != string::npos) - { - path_ = exe_.substr(0, exe_.find_last_of("/") + 1); - exe_ = exe_.substr(exe_.find_last_of("/") + 1); - } + if (exe_.find("/") != string::npos) + { + path_ = exe_.substr(0, exe_.find_last_of("/") + 1); + exe_ = exe_.substr(exe_.find_last_of("/") + 1); + } - /// kill if it's already running - execGetOutput("killall " + exe_); + /// kill if it's already running + execGetOutput("killall " + exe_); } void SpotifyStream::onStderrMsg(const char* buffer, size_t n) { - static bool libreelec_patched = false; - smatch m; + static bool libreelec_patched = false; + smatch m; - // Watch stderr for 'Loading track' messages and set the stream metadata - // For more than track name check: https://github.com/plietar/librespot/issues/154 + // Watch stderr for 'Loading track' messages and set the stream metadata + // For more than track name check: https://github.com/plietar/librespot/issues/154 - /// Watch will kill librespot if there was no message received for 130min - // 2016-11-02 22-05-15 [out] TRACE:librespot::stream: allocated stream 3580 - // 2016-11-02 22-05-15 [Debug] DEBUG:librespot::audio_file2: Got channel 3580 - // 2016-11-02 22-06-39 [out] DEBUG:librespot::spirc: kMessageTypeHello "SM-G901F" 5e1ffdd73f0d1741c4a173d5b238826464ca8e2f 1 0 - // 2016-11-02 22-06-39 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 123 1478120652755 - // 2016-11-02 22-06-40 [out] DEBUG:librespot::spirc: kMessageTypeNotify "SM-G901F" 5e1ffdd73f0d1741c4a173d5b238826464ca8e2f 1 0 - // 2016-11-02 22-06-41 [out] DEBUG:librespot::spirc: kMessageTypePause "SM-G901F" 5e1ffdd73f0d1741c4a173d5b238826464ca8e2f 2 0 - // 2016-11-02 22-06-42 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 124 1478120801615 - // 2016-11-02 22-06-47 [out] DEBUG:librespot::spirc: kMessageTypeNotify "SM-G901F" 5e1ffdd73f0d1741c4a173d5b238826464ca8e2f 2 1478120801615 - // 2016-11-02 22-35-10 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 125 1478120801615 - // 2016-11-02 23-36-06 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 126 1478120801615 - // 2016-11-03 01-37-08 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 127 1478120801615 - // 2016-11-03 02-38-13 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 128 1478120801615 - // killall librespot - // 2016-11-03 09-00-18 [out] INFO:librespot::main_helper: librespot 6fa4e4d (2016-09-21). Built on 2016-10-27. - // 2016-11-03 09-00-18 [out] INFO:librespot::session: Connecting to AP lon3-accesspoint-a34.ap.spotify.com:443 - // 2016-11-03 09-00-18 [out] INFO:librespot::session: Authenticated ! - watchdog_->trigger(); - string logmsg = utils::string::trim_copy(string(buffer, n)); + /// Watch will kill librespot if there was no message received for 130min + // 2016-11-02 22-05-15 [out] TRACE:librespot::stream: allocated stream 3580 + // 2016-11-02 22-05-15 [Debug] DEBUG:librespot::audio_file2: Got channel 3580 + // 2016-11-02 22-06-39 [out] DEBUG:librespot::spirc: kMessageTypeHello "SM-G901F" 5e1ffdd73f0d1741c4a173d5b238826464ca8e2f 1 0 + // 2016-11-02 22-06-39 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 123 1478120652755 + // 2016-11-02 22-06-40 [out] DEBUG:librespot::spirc: kMessageTypeNotify "SM-G901F" 5e1ffdd73f0d1741c4a173d5b238826464ca8e2f 1 0 + // 2016-11-02 22-06-41 [out] DEBUG:librespot::spirc: kMessageTypePause "SM-G901F" 5e1ffdd73f0d1741c4a173d5b238826464ca8e2f 2 0 + // 2016-11-02 22-06-42 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 124 1478120801615 + // 2016-11-02 22-06-47 [out] DEBUG:librespot::spirc: kMessageTypeNotify "SM-G901F" 5e1ffdd73f0d1741c4a173d5b238826464ca8e2f 2 1478120801615 + // 2016-11-02 22-35-10 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 125 1478120801615 + // 2016-11-02 23-36-06 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 126 1478120801615 + // 2016-11-03 01-37-08 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 127 1478120801615 + // 2016-11-03 02-38-13 [out] DEBUG:librespot::spirc: kMessageTypeNotify "Snapcast" 68724ecccd67781303655c49a73b74c5968667b1 128 1478120801615 + // killall librespot + // 2016-11-03 09-00-18 [out] INFO:librespot::main_helper: librespot 6fa4e4d (2016-09-21). Built on 2016-10-27. + // 2016-11-03 09-00-18 [out] INFO:librespot::session: Connecting to AP lon3-accesspoint-a34.ap.spotify.com:443 + // 2016-11-03 09-00-18 [out] INFO:librespot::session: Authenticated ! + watchdog_->trigger(); + string logmsg = utils::string::trim_copy(string(buffer, n)); - if ((logmsg.find("allocated stream") == string::npos) && - (logmsg.find("Got channel") == string::npos) && - (logmsg.find('\0') == string::npos) && - (logmsg.size() > 4)) - { - LOG(INFO) << "(" << getName() << ") " << logmsg << "\n"; - } + if ((logmsg.find("allocated stream") == string::npos) && (logmsg.find("Got channel") == string::npos) && (logmsg.find('\0') == string::npos) && + (logmsg.size() > 4)) + { + LOG(INFO) << "(" << getName() << ") " << logmsg << "\n"; + } - // Librespot patch: - // info!("metadata:{{\"ARTIST\":\"{}\",\"TITLE\":\"{}\"}}", artist.name, track.name); - // non patched: - // info!("Track \"{}\" loaded", track.name); + // Librespot patch: + // info!("metadata:{{\"ARTIST\":\"{}\",\"TITLE\":\"{}\"}}", artist.name, track.name); + // non patched: + // info!("Track \"{}\" loaded", track.name); - // If we detect a patched libreelec we don't want to bother with this anymoer - // to avoid duplicate metadata pushes - if (!libreelec_patched) - { - static regex re_nonpatched("Track \"(.*)\" loaded"); - if(regex_search(logmsg, m, re_nonpatched)) - { - LOG(INFO) << "metadata: <" << m[1] << ">\n"; + // If we detect a patched libreelec we don't want to bother with this anymoer + // to avoid duplicate metadata pushes + if (!libreelec_patched) + { + static regex re_nonpatched("Track \"(.*)\" loaded"); + if (regex_search(logmsg, m, re_nonpatched)) + { + LOG(INFO) << "metadata: <" << m[1] << ">\n"; - json jtag = { - {"TITLE", (string)m[1]} - }; - setMeta(jtag); - } - } + json jtag = {{"TITLE", (string)m[1]}}; + setMeta(jtag); + } + } - // Parse the patched version - static regex re_patched("metadata:(.*)"); - if (regex_search(logmsg, m, re_patched)) - { - LOG(INFO) << "metadata: <" << m[1] << ">\n"; + // Parse the patched version + static regex re_patched("metadata:(.*)"); + if (regex_search(logmsg, m, re_patched)) + { + LOG(INFO) << "metadata: <" << m[1] << ">\n"; - setMeta(json::parse(m[1].str())); - libreelec_patched = true; - } + setMeta(json::parse(m[1].str())); + libreelec_patched = true; + } } void SpotifyStream::stderrReader() { - watchdog_.reset(new Watchdog(this)); - /// 130min - watchdog_->start(130*60*1000); - ProcessStream::stderrReader(); + watchdog_.reset(new Watchdog(this)); + /// 130min + watchdog_->start(130 * 60 * 1000); + ProcessStream::stderrReader(); } void SpotifyStream::onTimeout(const Watchdog* watchdog, size_t ms) { - LOG(ERROR) << "Spotify timeout: " << ms / 1000 << "\n"; - if (process_) - process_->kill(); + LOG(ERROR) << "Spotify timeout: " << ms / 1000 << "\n"; + if (process_) + process_->kill(); } - - diff --git a/server/streamreader/spotifyStream.h b/server/streamreader/spotifyStream.h index eaeae8ef..2557c815 100644 --- a/server/streamreader/spotifyStream.h +++ b/server/streamreader/spotifyStream.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -28,23 +28,24 @@ * Implements EncoderListener to get the encoded data. * Data is passed to the PcmListener * usage: - * snapserver -s "spotify:///librespot?name=Spotify&username=&password=[&devicename=Snapcast][&bitrate=320][&volume=][&cache=]" + * snapserver -s "spotify:///librespot?name=Spotify&username=&password=[&devicename=Snapcast][&bitrate=320][&volume=][&cache=]" */ class SpotifyStream : public ProcessStream, WatchdogListener { public: - /// ctor. Encoded PCM data is passed to the PipeListener - SpotifyStream(PcmListener* pcmListener, const StreamUri& uri); - virtual ~SpotifyStream(); + /// ctor. Encoded PCM data is passed to the PipeListener + SpotifyStream(PcmListener* pcmListener, const StreamUri& uri); + virtual ~SpotifyStream(); protected: - std::unique_ptr watchdog_; + std::unique_ptr watchdog_; - virtual void stderrReader(); - virtual void onStderrMsg(const char* buffer, size_t n); - virtual void initExeAndPath(const std::string& filename); + virtual void stderrReader(); + virtual void onStderrMsg(const char* buffer, size_t n); + virtual void initExeAndPath(const std::string& filename); - virtual void onTimeout(const Watchdog* watchdog, size_t ms); + virtual void onTimeout(const Watchdog* watchdog, size_t ms); }; diff --git a/server/streamreader/streamManager.cpp b/server/streamreader/streamManager.cpp index cf904b16..3361b21c 100644 --- a/server/streamreader/streamManager.cpp +++ b/server/streamreader/streamManager.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -18,129 +18,128 @@ #include "streamManager.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 "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; -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) { } PcmStreamPtr StreamManager::addStream(const std::string& uri) { - StreamUri streamUri(uri); + StreamUri streamUri(uri); - if (streamUri.query.find("sampleformat") == streamUri.query.end()) - streamUri.query["sampleformat"] = sampleFormat_; + if (streamUri.query.find("sampleformat") == streamUri.query.end()) + streamUri.query["sampleformat"] = sampleFormat_; - if (streamUri.query.find("codec") == streamUri.query.end()) - streamUri.query["codec"] = codec_; + if (streamUri.query.find("codec") == streamUri.query.end()) + streamUri.query["codec"] = codec_; - if (streamUri.query.find("buffer_ms") == streamUri.query.end()) - streamUri.query["buffer_ms"] = cpt::to_string(readBufferMs_); + if (streamUri.query.find("buffer_ms") == streamUri.query.end()) + streamUri.query["buffer_ms"] = cpt::to_string(readBufferMs_); -// LOG(DEBUG) << "\nURI: " << streamUri.uri << "\nscheme: " << streamUri.scheme << "\nhost: " -// << streamUri.host << "\npath: " << streamUri.path << "\nfragment: " << streamUri.fragment << "\n"; + // LOG(DEBUG) << "\nURI: " << streamUri.uri << "\nscheme: " << streamUri.scheme << "\nhost: " + // << streamUri.host << "\npath: " << streamUri.path << "\nfragment: " << streamUri.fragment << "\n"; -// for (auto kv: streamUri.query) -// LOG(DEBUG) << "key: '" << kv.first << "' value: '" << kv.second << "'\n"; - PcmStreamPtr stream(nullptr); + // for (auto kv: streamUri.query) + // LOG(DEBUG) << "key: '" << kv.first << "' value: '" << kv.second << "'\n"; + PcmStreamPtr stream(nullptr); - if (streamUri.scheme == "pipe") - { - stream = make_shared(pcmListener_, streamUri); - } - else if (streamUri.scheme == "file") - { - stream = make_shared(pcmListener_, streamUri); - } - else if (streamUri.scheme == "process") - { - stream = make_shared(pcmListener_, streamUri); - } - else if (streamUri.scheme == "spotify") - { - stream = make_shared(pcmListener_, streamUri); - } - else if (streamUri.scheme == "airplay") - { - stream = make_shared(pcmListener_, streamUri); - } - else - { - throw SnapException("Unknown stream type: " + streamUri.scheme); - } + if (streamUri.scheme == "pipe") + { + stream = make_shared(pcmListener_, streamUri); + } + else if (streamUri.scheme == "file") + { + stream = make_shared(pcmListener_, streamUri); + } + else if (streamUri.scheme == "process") + { + stream = make_shared(pcmListener_, streamUri); + } + else if (streamUri.scheme == "spotify") + { + stream = make_shared(pcmListener_, streamUri); + } + else if (streamUri.scheme == "airplay") + { + stream = make_shared(pcmListener_, streamUri); + } + else + { + throw SnapException("Unknown stream type: " + streamUri.scheme); + } - if (stream) - { - for (auto s: streams_) - { - if (s->getName() == stream->getName()) - throw SnapException("Stream with name \"" + stream->getName() + "\" already exists"); - } - streams_.push_back(stream); - } + if (stream) + { + for (auto s : streams_) + { + if (s->getName() == stream->getName()) + throw SnapException("Stream with name \"" + stream->getName() + "\" already exists"); + } + streams_.push_back(stream); + } - return stream; + return stream; } const std::vector& StreamManager::getStreams() { - return streams_; + return streams_; } const PcmStreamPtr StreamManager::getDefaultStream() { - if (streams_.empty()) - return nullptr; + if (streams_.empty()) + return nullptr; - return streams_.front(); + return streams_.front(); } const PcmStreamPtr StreamManager::getStream(const std::string& id) { - for (auto stream: streams_) - { - if (stream->getId() == id) - return stream; - } - return nullptr; + for (auto stream : streams_) + { + if (stream->getId() == id) + return stream; + } + return nullptr; } void StreamManager::start() { - for (auto stream: streams_) - stream->start(); + for (auto stream : streams_) + stream->start(); } void StreamManager::stop() { - for (auto stream: streams_) - stream->stop(); + for (auto stream : streams_) + stream->stop(); } json StreamManager::toJson() const { - json result = json::array(); - for (auto stream: streams_) - result.push_back(stream->toJson()); - return result; + json result = json::array(); + for (auto stream : streams_) + result.push_back(stream->toJson()); + return result; } - - diff --git a/server/streamreader/streamManager.h b/server/streamreader/streamManager.h index b3755323..3c4742ce 100644 --- a/server/streamreader/streamManager.h +++ b/server/streamreader/streamManager.h @@ -1,32 +1,32 @@ #ifndef PCM_READER_FACTORY_H #define PCM_READER_FACTORY_H +#include "pcmStream.h" +#include #include #include -#include -#include "pcmStream.h" typedef std::shared_ptr PcmStreamPtr; class StreamManager { public: - StreamManager(PcmListener* pcmListener, const std::string& defaultSampleFormat, const std::string& defaultCodec, size_t defaultReadBufferMs = 20); + StreamManager(PcmListener* pcmListener, const std::string& defaultSampleFormat, const std::string& defaultCodec, size_t defaultReadBufferMs = 20); - PcmStreamPtr addStream(const std::string& uri); - void start(); - void stop(); - const std::vector& getStreams(); - const PcmStreamPtr getDefaultStream(); - const PcmStreamPtr getStream(const std::string& id); - json toJson() const; + PcmStreamPtr addStream(const std::string& uri); + void start(); + void stop(); + const std::vector& getStreams(); + const PcmStreamPtr getDefaultStream(); + const PcmStreamPtr getStream(const std::string& id); + json toJson() const; private: - std::vector streams_; - PcmListener* pcmListener_; - std::string sampleFormat_; - std::string codec_; - size_t readBufferMs_; + std::vector streams_; + PcmListener* pcmListener_; + std::string sampleFormat_; + std::string codec_; + size_t readBufferMs_; }; diff --git a/server/streamreader/streamUri.cpp b/server/streamreader/streamUri.cpp index 946a1bf5..559d6661 100644 --- a/server/streamreader/streamUri.cpp +++ b/server/streamreader/streamUri.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -17,9 +17,9 @@ ***/ #include "streamUri.h" -#include "common/utils/string_utils.h" -#include "common/strCompat.h" #include "aixlog.hpp" +#include "common/strCompat.h" +#include "common/utils/string_utils.h" using namespace std; @@ -28,123 +28,116 @@ namespace strutils = utils::string; StreamUri::StreamUri(const std::string& uri) { - parse(uri); + parse(uri); } void StreamUri::parse(const std::string& streamUri) { -// https://en.wikipedia.org/wiki/Uniform_Resource_Identifier -// scheme:[//[user:password@]host[:port]][/]path[?query][#fragment] -// would be more elegant with regex. Not yet supported on my dev machine's gcc 4.8 :( - LOG(DEBUG) << "StreamUri: " << streamUri << "\n"; - size_t pos; - uri = strutils::trim_copy(streamUri); - while (!uri.empty() && ((uri[0] == '\'') || (uri[0] == '"'))) - uri = uri.substr(1); - while (!uri.empty() && ((uri[uri.length()-1] == '\'') || (uri[uri.length()-1] == '"'))) - uri = uri.substr(0, this->uri.length()-1); + // https://en.wikipedia.org/wiki/Uniform_Resource_Identifier + // scheme:[//[user:password@]host[:port]][/]path[?query][#fragment] + // would be more elegant with regex. Not yet supported on my dev machine's gcc 4.8 :( + LOG(DEBUG) << "StreamUri: " << streamUri << "\n"; + size_t pos; + uri = strutils::trim_copy(streamUri); + while (!uri.empty() && ((uri[0] == '\'') || (uri[0] == '"'))) + uri = uri.substr(1); + while (!uri.empty() && ((uri[uri.length() - 1] == '\'') || (uri[uri.length() - 1] == '"'))) + uri = uri.substr(0, this->uri.length() - 1); - string decodedUri = strutils::uriDecode(uri); - LOG(DEBUG) << "StreamUri: " << decodedUri << "\n"; + string decodedUri = strutils::uriDecode(uri); + LOG(DEBUG) << "StreamUri: " << decodedUri << "\n"; - string tmp(decodedUri); + string tmp(decodedUri); - pos = tmp.find(':'); - if (pos == string::npos) - throw invalid_argument("missing ':'"); - scheme = strutils::trim_copy(tmp.substr(0, pos)); - tmp = tmp.substr(pos + 1); - LOG(DEBUG) << "scheme: '" << scheme << "' tmp: '" << tmp << "'\n"; + pos = tmp.find(':'); + if (pos == string::npos) + throw invalid_argument("missing ':'"); + scheme = strutils::trim_copy(tmp.substr(0, pos)); + tmp = tmp.substr(pos + 1); + LOG(DEBUG) << "scheme: '" << scheme << "' tmp: '" << tmp << "'\n"; - if (tmp.find("//") != 0) - throw invalid_argument("missing host separator: '//'"); - tmp = tmp.substr(2); + if (tmp.find("//") != 0) + throw invalid_argument("missing host separator: '//'"); + tmp = tmp.substr(2); - pos = tmp.find('/'); - if (pos == string::npos) - throw invalid_argument("missing path separator: '/'"); - host = strutils::trim_copy(tmp.substr(0, pos)); - tmp = tmp.substr(pos); - path = tmp; - LOG(DEBUG) << "host: '" << host << "' tmp: '" << tmp << "' path: '" << path << "'\n"; + pos = tmp.find('/'); + if (pos == string::npos) + throw invalid_argument("missing path separator: '/'"); + host = strutils::trim_copy(tmp.substr(0, pos)); + tmp = tmp.substr(pos); + path = tmp; + LOG(DEBUG) << "host: '" << host << "' tmp: '" << tmp << "' path: '" << path << "'\n"; - pos = tmp.find('?'); - if (pos == string::npos) - return; + pos = tmp.find('?'); + if (pos == string::npos) + return; - path = strutils::trim_copy(tmp.substr(0, pos)); - tmp = tmp.substr(pos + 1); - string queryStr = tmp; - LOG(DEBUG) << "path: '" << path << "' tmp: '" << tmp << "' query: '" << queryStr << "'\n"; + path = strutils::trim_copy(tmp.substr(0, pos)); + tmp = tmp.substr(pos + 1); + string queryStr = tmp; + LOG(DEBUG) << "path: '" << path << "' tmp: '" << tmp << "' query: '" << queryStr << "'\n"; - pos = tmp.find('#'); - if (pos != string::npos) - { - queryStr = tmp.substr(0, pos); - tmp = tmp.substr(pos + 1); - fragment = strutils::trim_copy(tmp); - LOG(DEBUG) << "query: '" << queryStr << "' fragment: '" << fragment << "' tmp: '" << tmp << "'\n"; - } + pos = tmp.find('#'); + if (pos != string::npos) + { + queryStr = tmp.substr(0, pos); + tmp = tmp.substr(pos + 1); + fragment = strutils::trim_copy(tmp); + LOG(DEBUG) << "query: '" << queryStr << "' fragment: '" << fragment << "' tmp: '" << tmp << "'\n"; + } - vector keyValueList = strutils::split(queryStr, '&'); - for (auto& kv: keyValueList) - { - pos = kv.find('='); - if (pos != string::npos) - { - string key = strutils::trim_copy(kv.substr(0, pos)); - string value = strutils::trim_copy(kv.substr(pos+1)); - query[key] = value; - } - } - LOG(DEBUG) << "StreamUri.toString: " << toString() << "\n"; + vector keyValueList = strutils::split(queryStr, '&'); + for (auto& kv : keyValueList) + { + pos = kv.find('='); + if (pos != string::npos) + { + string key = strutils::trim_copy(kv.substr(0, pos)); + string value = strutils::trim_copy(kv.substr(pos + 1)); + query[key] = value; + } + } + LOG(DEBUG) << "StreamUri.toString: " << toString() << "\n"; } std::string StreamUri::toString() const { -// scheme:[//[user:password@]host[:port]][/]path[?query][#fragment] - stringstream ss; - ss << scheme << "://" << host << "/" + path; - if (!query.empty()) - { - ss << "?"; - auto iter = query.begin(); - while (true) - { - ss << iter->first << "=" << iter->second; - if (++iter == query.end()) - break; - ss << "&"; - } - } - if (!fragment.empty()) - ss << "#" << fragment; + // scheme:[//[user:password@]host[:port]][/]path[?query][#fragment] + stringstream ss; + ss << scheme << "://" << host << "/" + path; + if (!query.empty()) + { + ss << "?"; + auto iter = query.begin(); + while (true) + { + ss << iter->first << "=" << iter->second; + if (++iter == query.end()) + break; + ss << "&"; + } + } + if (!fragment.empty()) + ss << "#" << fragment; - return ss.str(); + return ss.str(); } json StreamUri::toJson() const { - json j = { - {"raw", toString()}, - {"scheme", scheme}, - {"host", host}, - {"path", path}, - {"fragment", fragment}, - {"query", query} - }; - return j; + json j = {{"raw", toString()}, {"scheme", scheme}, {"host", host}, {"path", path}, {"fragment", fragment}, {"query", query}}; + return j; } std::string StreamUri::getQuery(const std::string& key, const std::string& def) const { - auto iter = query.find(key); - if (iter != query.end()) - return iter->second; - return def; + auto iter = query.find(key); + if (iter != query.end()) + return iter->second; + return def; } diff --git a/server/streamreader/streamUri.h b/server/streamreader/streamUri.h index 96f864b7..b8875549 100644 --- a/server/streamreader/streamUri.h +++ b/server/streamreader/streamUri.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ #ifndef READER_URI_H #define READER_URI_H -#include #include +#include #include "common/json.hpp" @@ -31,29 +31,29 @@ using json = nlohmann::json; // scheme:[//[user:password@]host[:port]][/]path[?query][#fragment] struct StreamUri { - StreamUri(const std::string& uri); - std::string uri; - std::string scheme; -/* struct Authority - { - std::string username; - std::string password; - std::string host; - size_t port; - }; - Authority authority; -*/ - std::string host; - std::string path; - std::map query; - std::string fragment; + StreamUri(const std::string& uri); + std::string uri; + std::string scheme; + /* struct Authority + { + std::string username; + std::string password; + std::string host; + size_t port; + }; + Authority authority; + */ + std::string host; + std::string path; + std::map query; + std::string fragment; - std::string id() const; - json toJson() const; - std::string getQuery(const std::string& key, const std::string& def = "") const; + std::string id() const; + json toJson() const; + std::string getQuery(const std::string& key, const std::string& def = "") const; - void parse(const std::string& streamUri); - std::string toString() const; + void parse(const std::string& streamUri); + std::string toString() const; }; diff --git a/server/streamreader/watchdog.cpp b/server/streamreader/watchdog.cpp index f51b8496..44ed5dd4 100644 --- a/server/streamreader/watchdog.cpp +++ b/server/streamreader/watchdog.cpp @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -30,54 +30,53 @@ Watchdog::Watchdog(WatchdogListener* listener) : listener_(listener), thread_(nu Watchdog::~Watchdog() { - stop(); + stop(); } void Watchdog::start(size_t timeoutMs) { - timeoutMs_ = timeoutMs; - if (!thread_ || !active_) - { - active_ = true; - thread_.reset(new thread(&Watchdog::worker, this)); - } - else - trigger(); + timeoutMs_ = timeoutMs; + if (!thread_ || !active_) + { + active_ = true; + thread_.reset(new thread(&Watchdog::worker, this)); + } + else + trigger(); } void Watchdog::stop() { - active_ = false; - trigger(); - if (thread_ && thread_->joinable()) - thread_->join(); - thread_ = nullptr; + active_ = false; + trigger(); + if (thread_ && thread_->joinable()) + thread_->join(); + thread_ = nullptr; } void Watchdog::trigger() { -// std::unique_lock lck(mtx_); - cv_.notify_one(); + // std::unique_lock lck(mtx_); + cv_.notify_one(); } void Watchdog::worker() { - while (active_) - { - std::unique_lock lck(mtx_); - if (cv_.wait_for(lck, std::chrono::milliseconds(timeoutMs_)) == std::cv_status::timeout) - { - if (listener_) - { - listener_->onTimeout(this, timeoutMs_); - break; - } - } - } - active_ = false; + while (active_) + { + std::unique_lock lck(mtx_); + if (cv_.wait_for(lck, std::chrono::milliseconds(timeoutMs_)) == std::cv_status::timeout) + { + if (listener_) + { + listener_->onTimeout(this, timeoutMs_); + break; + } + } + } + active_ = false; } - diff --git a/server/streamreader/watchdog.h b/server/streamreader/watchdog.h index 59137239..528cced9 100644 --- a/server/streamreader/watchdog.h +++ b/server/streamreader/watchdog.h @@ -1,6 +1,6 @@ /*** 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 it under the terms of the GNU General Public License as published by @@ -19,11 +19,11 @@ #ifndef WATCH_DOG_H #define WATCH_DOG_H -#include -#include -#include #include #include +#include +#include +#include class Watchdog; @@ -32,7 +32,7 @@ class Watchdog; class WatchdogListener { public: - virtual void onTimeout(const Watchdog* watchdog, size_t ms) = 0; + virtual void onTimeout(const Watchdog* watchdog, size_t ms) = 0; }; @@ -40,24 +40,23 @@ public: class Watchdog { public: - Watchdog(WatchdogListener* listener = nullptr); - virtual ~Watchdog(); + Watchdog(WatchdogListener* listener = nullptr); + virtual ~Watchdog(); - void start(size_t timeoutMs); - void stop(); - void trigger(); + void start(size_t timeoutMs); + void stop(); + void trigger(); private: - WatchdogListener* listener_; - std::condition_variable cv_; - std::mutex mtx_; - std::unique_ptr thread_; - size_t timeoutMs_; - std::atomic active_; + WatchdogListener* listener_; + std::condition_variable cv_; + std::mutex mtx_; + std::unique_ptr thread_; + size_t timeoutMs_; + std::atomic active_; - void worker(); + void worker(); }; #endif -