diff --git a/client/Makefile b/client/Makefile index def5c69b..0af31916 100644 --- a/client/Makefile +++ b/client/Makefile @@ -1,9 +1,9 @@ VERSION = 0.1 CC = /usr/bin/g++ CFLAGS = -std=gnu++0x -static-libgcc -static-libstdc++ -Wall -Wno-unused-function -O3 -D_REENTRANT -DVERSION=\"$(VERSION)\" -I.. -LDFLAGS = -lrt -lpthread -lboost_system -lboost_program_options -lasound -logg -lvorbis -lvorbisenc -lFLAC +LDFLAGS = -lrt -lpthread -lboost_system -lboost_program_options -lasound -logg -lvorbis -lvorbisenc -lFLAC -lavahi-client -lavahi-common -OBJ = snapClient.o stream.o alsaPlayer.o clientConnection.o timeProvider.o oggDecoder.o pcmDecoder.o flacDecoder.o controller.o ../message/pcmChunk.o ../common/log.o ../message/sampleFormat.o +OBJ = snapClient.o stream.o alsaPlayer.o clientConnection.o timeProvider.o oggDecoder.o pcmDecoder.o flacDecoder.o controller.o browseAvahi.o ../message/pcmChunk.o ../common/log.o ../message/sampleFormat.o BIN = snapclient all: client diff --git a/client/browseAvahi.cpp b/client/browseAvahi.cpp index 35282338..941c0435 100644 --- a/client/browseAvahi.cpp +++ b/client/browseAvahi.cpp @@ -17,25 +17,33 @@ USA. ***/ -#ifdef HAVE_CONFIG_H -#include -#endif +#include "browseAvahi.h" +#include +#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include static AvahiSimplePoll *simple_poll = NULL; -static void resolve_callback( + +BrowseAvahi::BrowseAvahi() : client_(NULL), sb_(NULL) +{ +} + + +BrowseAvahi::~BrowseAvahi() +{ + if (sb_) + avahi_service_browser_free(sb_); + + if (client_) + avahi_client_free(client_); + + if (simple_poll) + avahi_simple_poll_free(simple_poll); +} + + +void BrowseAvahi::resolve_callback( AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, @@ -50,6 +58,7 @@ static void resolve_callback( AvahiLookupResultFlags flags, AVAHI_GCC_UNUSED void* userdata) { + BrowseAvahi* browseAvahi = static_cast(userdata); assert(r); /* Called whenever a service has been resolved successfully or timed out */ @@ -63,8 +72,14 @@ static void resolve_callback( char a[AVAHI_ADDRESS_STR_MAX], *t; fprintf(stderr, "Service '%s' of type '%s' in domain '%s':\n", name, type, domain); - + avahi_address_snprint(a, sizeof(a), address); + browseAvahi->result_.host_ = host_name; + browseAvahi->result_.ip_ = a; + browseAvahi->result_.port_ = port; + browseAvahi->result_.proto_ = protocol; + browseAvahi->result_.valid_ = true; + t = avahi_string_list_to_string(txt); fprintf(stderr, "\t%s:%u (%s)\n" @@ -93,7 +108,8 @@ static void resolve_callback( avahi_service_resolver_free(r); } -static void browse_callback( + +void BrowseAvahi::browse_callback( AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, @@ -104,7 +120,8 @@ static void browse_callback( AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata) { - AvahiClient *c = (AvahiClient*)userdata; +// 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 */ @@ -124,8 +141,8 @@ static void browse_callback( the callback function is called the server will free the resolver for us. */ - if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0, resolve_callback, c))) - fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c))); + if (!(avahi_service_resolver_new(browseAvahi->client_, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0, resolve_callback, userdata))) + fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(browseAvahi->client_))); break; @@ -140,10 +157,12 @@ static void browse_callback( } } -static void 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); /* Called whenever the client or server state changes */ +// BrowseAvahi* browseAvahi = static_cast(userdata); if (state == AVAHI_CLIENT_FAILURE) { fprintf(stderr, "Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c))); @@ -151,11 +170,10 @@ static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UN } } -int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) { - AvahiClient *client = NULL; - AvahiServiceBrowser *sb = NULL; + +bool BrowseAvahi::browse(const std::string& serviceName, int proto, AvahiResult& result, int timeout) +{ int error; - int ret = 1; /* Allocate main loop object */ if (!(simple_poll = avahi_simple_poll_new())) { @@ -164,38 +182,51 @@ int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) { } /* Allocate a new client */ - client = avahi_client_new(avahi_simple_poll_get(simple_poll), (AvahiClientFlags)0, client_callback, NULL, &error); + client_ = avahi_client_new(avahi_simple_poll_get(simple_poll), (AvahiClientFlags)0, client_callback, this, &error); /* Check wether creating the client object succeeded */ - if (!client) { + if (!client_) { fprintf(stderr, "Failed to create client: %s\n", avahi_strerror(error)); goto fail; } /* Create the service browser */ - if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_snapcast._tcp", NULL, (AvahiLookupFlags)0, browse_callback, client))) { - fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client))); + if (!(sb_ = avahi_service_browser_new(client_, AVAHI_IF_UNSPEC, proto, serviceName.c_str(), NULL, (AvahiLookupFlags)0, browse_callback, this))) { + fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client_))); goto fail; } - /* Run the main loop */ - avahi_simple_poll_loop(simple_poll); - - ret = 0; + result_.valid_ = false; + while (timeout > 0) + { + avahi_simple_poll_iterate(simple_poll, 100); + timeout -= 100; + if (result_.valid_) + { + result = result_; + return true; + } + } fail: - - /* Cleanup things */ - if (sb) - avahi_service_browser_free(sb); - - if (client) - avahi_client_free(client); - - if (simple_poll) - avahi_simple_poll_free(simple_poll); - - return ret; + return false; } +/* +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) +{ + std::string ip; + size_t port; + BrowseAvahi browseAvahi; + if (browseAvahi.browse("_snapcast._tcp", AVAHI_PROTO_INET, ip, port, 5000)) + std::cout << ip << ":" << port << "\n"; + + return 0; +} +*/ + + + + + diff --git a/client/browseAvahi.h b/client/browseAvahi.h new file mode 100644 index 00000000..9ba61e85 --- /dev/null +++ b/client/browseAvahi.h @@ -0,0 +1,63 @@ +/*** + This file is part of avahi. + + avahi is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + avahi is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General + Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + + +struct AvahiResult +{ + int proto_; + std::string ip_; + std::string host_; + size_t port_; + bool valid_; +}; + + +class BrowseAvahi +{ +public: + BrowseAvahi(); + ~BrowseAvahi(); + bool browse(const std::string& serviceName, int proto, AvahiResult& result, int timeout); + +private: + 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_; + AvahiResult result_; + AvahiServiceBrowser* sb_; +}; + + diff --git a/client/snapClient.cpp b/client/snapClient.cpp index cabfc857..6c579cca 100644 --- a/client/snapClient.cpp +++ b/client/snapClient.cpp @@ -14,6 +14,7 @@ #include "common/signalHandler.h" #include "controller.h" #include "alsaPlayer.h" +#include "browseAvahi.h" using namespace std; @@ -61,7 +62,7 @@ int main (int argc, char *argv[]) ("help,h", "produce help message") ("version,v", "show version number") ("list,l", po::bool_switch(&listPcmDevices)->default_value(false), "list pcm devices") - ("ip,i", po::value(&ip)->default_value("localhost"), "server IP") + ("ip,i", po::value(&ip), "server IP") ("port,p", po::value(&port)->default_value(98765), "server port") ("soundcard,s", po::value(&soundcard)->default_value("default"), "index or name of the soundcard") ("daemon,d", po::bool_switch(&runAsDaemon)->default_value(false), "daemonize") @@ -119,14 +120,30 @@ int main (int argc, char *argv[]) return 1; } + if (!vm.count("ip")) + { + BrowseAvahi browseAvahi; + AvahiResult avahiResult; + while (!g_terminated) + { + if (browseAvahi.browse("_snapcast._tcp", AVAHI_PROTO_INET, avahiResult, 5000)) + { + ip = avahiResult.ip_; + port = avahiResult.port_; + std::cout << ip << ":" << port << "\n"; + break; + } + } + } Controller controller; - controller.start(pcmDevice, ip, port, latency); - - while(!g_terminated) - usleep(100*1000); - - controller.stop(); + if (!g_terminated) + { + controller.start(pcmDevice, ip, port, latency); + while(!g_terminated) + usleep(100*1000); + controller.stop(); + } daemonShutdown(); return 0; diff --git a/common/avahiService.h b/common/avahiService.h new file mode 100644 index 00000000..dcd66ed8 --- /dev/null +++ b/common/avahiService.h @@ -0,0 +1,21 @@ +#ifndef AVAHI_SERVICE_H +#define AVAHI_SERVICE_H + +#include +#include + +struct AvahiService +{ + AvahiService(const std::string& name, size_t port, int proto = AVAHI_PROTO_UNSPEC) : name_(name), port_(port), proto_(proto) + { + } + + std::string name_; + size_t port_; + int proto_; +}; + + +#endif + + diff --git a/init.d/snapclient b/init.d/snapclient index e6af31d8..46efdab6 100755 --- a/init.d/snapclient +++ b/init.d/snapclient @@ -21,9 +21,8 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin:/ DESC="Snapclient" NAME=snapclient -SERVERIP=_SERVER_HOST_ DAEMON=/usr/sbin/$NAME -DAEMON_ARGS="-d -i $SERVERIP" +DAEMON_ARGS="-d" PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME diff --git a/server/Makefile b/server/Makefile index 5fa6973c..82d71027 100644 --- a/server/Makefile +++ b/server/Makefile @@ -1,9 +1,9 @@ VERSION = 0.1 CC = /usr/bin/g++ CFLAGS = -std=gnu++0x -Wall -Wno-unused-function -O3 -D_REENTRANT -DVERSION=\"$(VERSION)\" -I.. -LDFLAGS = -lrt -lpthread -lboost_system -lboost_program_options -lvorbis -lvorbisenc -logg -lFLAC +LDFLAGS = -lrt -lpthread -lboost_system -lboost_program_options -lvorbis -lvorbisenc -logg -lFLAC -lavahi-client -lavahi-common -OBJ = snapServer.o controlServer.o flacEncoder.o pcmEncoder.o oggEncoder.o serverSession.o ../common/log.o ../message/pcmChunk.o ../message/sampleFormat.o +OBJ = snapServer.o controlServer.o flacEncoder.o pcmEncoder.o oggEncoder.o serverSession.o publishAvahi.o ../common/log.o ../message/pcmChunk.o ../message/sampleFormat.o BIN = snapserver all: server diff --git a/server/publishAvahi.cpp b/server/publishAvahi.cpp index 7a07f29a..13ea3338 100644 --- a/server/publishAvahi.cpp +++ b/server/publishAvahi.cpp @@ -17,12 +17,10 @@ USA. ***/ -#include "publishAvahi.h" - -#include #include #include #include +#include #include #include @@ -33,16 +31,66 @@ #include #include -#include +#include "publishAvahi.h" -PublishAvahi::PublishAvahi() : group(NULL), simple_poll(NULL), name(NULL) +static AvahiEntryGroup *group; +static AvahiSimplePoll *simple_poll; +static char* name; + +PublishAvahi::PublishAvahi(const std::string& serviceName) : client(NULL), serviceName_(serviceName) { + group = NULL; + simple_poll = NULL; + name = avahi_strdup(serviceName_.c_str()); +} + + +void PublishAvahi::publish(const std::vector& services) +{ + this->services = services; + + AvahiClient *client = NULL; + int error; + + /* Allocate main loop object */ + if (!(simple_poll = avahi_simple_poll_new())) + { + fprintf(stderr, "Failed to create simple poll object.\n"); + } + + /* Allocate a new client */ + 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) + { + fprintf(stderr, "Failed to create client: %s\n", avahi_strerror(error)); + } + + active_ = true; + pollThread_ = std::thread(&PublishAvahi::worker, this); +} + + +void PublishAvahi::worker() +{ + while (active_ && (avahi_simple_poll_iterate(simple_poll, 100) == 0)); } PublishAvahi::~PublishAvahi() { + active_ = false; + pollThread_.join(); + + if (client) + avahi_client_free(client); + + if (simple_poll) + avahi_simple_poll_free(simple_poll); + + avahi_free(name); } @@ -70,7 +118,7 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState fprintf(stderr, "Service name collision, renaming service to '%s'\n", name); /* And recreate the services */ - create_services(avahi_entry_group_get_client(g)); + static_cast(userdata)->create_services(avahi_entry_group_get_client(g)); break; } @@ -98,7 +146,7 @@ void PublishAvahi::create_services(AvahiClient *c) { if (!group) { - if (!(group = avahi_entry_group_new(c, (void(*)(AvahiEntryGroup*, AvahiEntryGroupState, void*))std::bind(&PublishAvahi::entry_group_callback, this), NULL))) { + if (!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) { fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_client_errno(c))); goto fail; } @@ -128,14 +176,18 @@ void PublishAvahi::create_services(AvahiClient *c) { } */ /* Add the same service for BSD LPR */ - if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, "_snapcast._tcp", NULL, NULL, 515, NULL)) < 0) { + for (size_t n=0; n(userdata)->create_services(c); break; case AVAHI_CLIENT_FAILURE: @@ -214,58 +267,17 @@ void PublishAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI } } - -int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) { - PublishAvahi publishAvahi; - - AvahiClient *client = NULL; - int error; - int ret = 1; - struct timeval tv; - - /* Allocate main loop object */ - if (!(simple_poll = avahi_simple_poll_new())) { - fprintf(stderr, "Failed to create simple poll object.\n"); - goto fail; - } - - name = avahi_strdup("MegaPrinter"); - - /* Allocate a new client */ - client = avahi_client_new(avahi_simple_poll_get(simple_poll), AVAHI_CLIENT_IGNORE_USER_CONFIG, client_callback, NULL, &error); - - /* Check wether creating the client object succeeded */ - if (!client) { - fprintf(stderr, "Failed to create client: %s\n", avahi_strerror(error)); - goto fail; - } - - /* After 10s do some weird modification to the service */ -/* avahi_simple_poll_get(simple_poll)->timeout_new( - avahi_simple_poll_get(simple_poll), - avahi_elapse_time(&tv, 1000*10, 0), - modify_callback, - client); -*/ - /* Run the main loop */ - while (avahi_simple_poll_iterate(simple_poll, 100) == 0) - printf("1"); - - ret = 0; - -fail: - - /* Cleanup things */ - - if (client) - avahi_client_free(client); - - if (simple_poll) - avahi_simple_poll_free(simple_poll); - - avahi_free(name); - - return ret; +/* +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) +{ + PublishAvahi publishAvahi("SnapCast"); + std::vector services; + services.push_back(AvahiService("_snapcast._tcp", 123)); + publishAvahi.publish(services); + while (true) + usleep(100000); + return 0; } +*/ diff --git a/server/publishAvahi.h b/server/publishAvahi.h index a3098395..4e39c1c9 100644 --- a/server/publishAvahi.h +++ b/server/publishAvahi.h @@ -10,21 +10,40 @@ #include #include #include +#include +#include +#include + + +struct AvahiService +{ + AvahiService(const std::string& name, size_t port, int proto = AVAHI_PROTO_UNSPEC) : name_(name), port_(port), proto_(proto) + { + } + + std::string name_; + size_t port_; + int proto_; +}; + class PublishAvahi { public: - PublishAvahi(); + PublishAvahi(const std::string& serviceName); ~PublishAvahi(); - -private: - void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata); - void create_services(AvahiClient *c); - void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata); + void publish(const std::vector& services); + std::vector services; - AvahiEntryGroup *group; - AvahiSimplePoll *simple_poll; - char* name; +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); + AvahiClient* client; + std::string serviceName_; + std::thread pollThread_; + void worker(); + std::atomic active_; }; diff --git a/server/snapServer.cpp b/server/snapServer.cpp index 5ffd202d..1d075e56 100644 --- a/server/snapServer.cpp +++ b/server/snapServer.cpp @@ -12,6 +12,7 @@ #include "oggEncoder.h" #include "flacEncoder.h" #include "controlServer.h" +#include "publishAvahi.h" bool g_terminated = false; @@ -113,6 +114,12 @@ int main(int argc, char* argv[]) signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); + PublishAvahi publishAvahi("SnapCast"); + std::vector services; + services.push_back(AvahiService("_snapcast._tcp", port)); + publishAvahi.publish(services); + + while (!g_terminated) { int fd = open(fifoName.c_str(), O_RDONLY | O_NONBLOCK);