mirror of
https://github.com/badaix/snapcast.git
synced 2025-07-12 22:27:44 +02:00
add clang-format file
reformat code
This commit is contained in:
parent
b733f646ea
commit
b20add3815
105 changed files with 7773 additions and 7723 deletions
20
.clang-format
Normal file
20
.clang-format
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
AccessModifierOffset: '-4'
|
||||||
|
AllowShortBlocksOnASingleLine: 'false'
|
||||||
|
AllowShortCaseLabelsOnASingleLine: 'false'
|
||||||
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AllowShortIfStatementsOnASingleLine: 'false'
|
||||||
|
AllowShortLoopsOnASingleLine: 'false'
|
||||||
|
AlwaysBreakTemplateDeclarations: 'true'
|
||||||
|
BreakBeforeBraces: Allman
|
||||||
|
ColumnLimit: '160'
|
||||||
|
IndentCaseLabels: 'true'
|
||||||
|
IndentWidth: '4'
|
||||||
|
Language: Cpp
|
||||||
|
MaxEmptyLinesToKeep: '3'
|
||||||
|
PenaltyBreakComment: '100000'
|
||||||
|
PointerAlignment: Left
|
||||||
|
Standard: Cpp11
|
||||||
|
UseTab: Never
|
||||||
|
|
||||||
|
...
|
|
@ -1,5 +1,5 @@
|
||||||
# This file is part of snapcast
|
# This file is part of snapcast
|
||||||
# Copyright (C) 2014-2018 Johannes Pohl
|
# Copyright (C) 2014-2019 Johannes Pohl
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
|
57
client/browseZeroConf/browseAvahi.cpp
Executable file → Normal file
57
client/browseZeroConf/browseAvahi.cpp
Executable file → Normal file
|
@ -18,16 +18,16 @@
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include "browseAvahi.h"
|
#include "browseAvahi.h"
|
||||||
#include <stdio.h>
|
#include "aixlog.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <iostream>
|
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "aixlog.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
static AvahiSimplePoll *simple_poll = NULL;
|
static AvahiSimplePoll* simple_poll = NULL;
|
||||||
|
|
||||||
|
|
||||||
BrowseAvahi::BrowseAvahi() : client_(NULL), sb_(NULL)
|
BrowseAvahi::BrowseAvahi() : client_(NULL), sb_(NULL)
|
||||||
|
@ -57,19 +57,9 @@ void BrowseAvahi::cleanUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void BrowseAvahi::resolve_callback(
|
void BrowseAvahi::resolve_callback(AvahiServiceResolver* r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol,
|
||||||
AvahiServiceResolver *r,
|
AvahiResolverEvent event, const char* name, const char* type, const char* domain, const char* host_name,
|
||||||
AVAHI_GCC_UNUSED AvahiIfIndex interface,
|
const AvahiAddress* address, uint16_t port, AvahiStringList* txt, AvahiLookupResultFlags flags,
|
||||||
AVAHI_GCC_UNUSED AvahiProtocol protocol,
|
|
||||||
AvahiResolverEvent event,
|
|
||||||
const char *name,
|
|
||||||
const char *type,
|
|
||||||
const char *domain,
|
|
||||||
const char *host_name,
|
|
||||||
const AvahiAddress *address,
|
|
||||||
uint16_t port,
|
|
||||||
AvahiStringList *txt,
|
|
||||||
AvahiLookupResultFlags flags,
|
|
||||||
AVAHI_GCC_UNUSED void* userdata)
|
AVAHI_GCC_UNUSED void* userdata)
|
||||||
{
|
{
|
||||||
BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata);
|
BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata);
|
||||||
|
@ -80,7 +70,8 @@ void BrowseAvahi::resolve_callback(
|
||||||
switch (event)
|
switch (event)
|
||||||
{
|
{
|
||||||
case AVAHI_RESOLVER_FAILURE:
|
case AVAHI_RESOLVER_FAILURE:
|
||||||
LOG(ERROR) << "(Resolver) Failed to resolve service '" << name << "' of type '" << type << "' in domain '" << domain << "': " << avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))) << "\n";
|
LOG(ERROR) << "(Resolver) Failed to resolve service '" << name << "' of type '" << type << "' in domain '" << domain
|
||||||
|
<< "': " << avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))) << "\n";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AVAHI_RESOLVER_FOUND:
|
case AVAHI_RESOLVER_FOUND:
|
||||||
|
@ -94,7 +85,7 @@ void BrowseAvahi::resolve_callback(
|
||||||
browseAvahi->result_.ip = a;
|
browseAvahi->result_.ip = a;
|
||||||
browseAvahi->result_.port = port;
|
browseAvahi->result_.port = port;
|
||||||
// protocol seems to be unreliable (0 for IPv4 and for IPv6)
|
// protocol seems to be unreliable (0 for IPv4 and for IPv6)
|
||||||
browseAvahi->result_.ip_version = (browseAvahi->result_.ip.find(":") == std::string::npos)?(IPVersion::IPv4):(IPVersion::IPv6);
|
browseAvahi->result_.ip_version = (browseAvahi->result_.ip.find(":") == std::string::npos) ? (IPVersion::IPv4) : (IPVersion::IPv6);
|
||||||
browseAvahi->result_.valid = true;
|
browseAvahi->result_.valid = true;
|
||||||
browseAvahi->result_.iface_idx = interface;
|
browseAvahi->result_.iface_idx = interface;
|
||||||
|
|
||||||
|
@ -116,19 +107,11 @@ void BrowseAvahi::resolve_callback(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void BrowseAvahi::browse_callback(
|
void BrowseAvahi::browse_callback(AvahiServiceBrowser* b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char* name,
|
||||||
AvahiServiceBrowser *b,
|
const char* type, const char* domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata)
|
||||||
AvahiIfIndex interface,
|
|
||||||
AvahiProtocol protocol,
|
|
||||||
AvahiBrowserEvent event,
|
|
||||||
const char *name,
|
|
||||||
const char *type,
|
|
||||||
const char *domain,
|
|
||||||
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
|
|
||||||
void* userdata)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
// AvahiClient* client = (AvahiClient*)userdata;
|
// AvahiClient* client = (AvahiClient*)userdata;
|
||||||
BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata);
|
BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata);
|
||||||
assert(b);
|
assert(b);
|
||||||
|
|
||||||
|
@ -149,7 +132,8 @@ void BrowseAvahi::browse_callback(
|
||||||
the callback function is called the server will free
|
the callback function is called the server will free
|
||||||
the resolver for us. */
|
the resolver for us. */
|
||||||
|
|
||||||
if (!(avahi_service_resolver_new(browseAvahi->client_, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0, resolve_callback, userdata)))
|
if (!(avahi_service_resolver_new(browseAvahi->client_, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0,
|
||||||
|
resolve_callback, userdata)))
|
||||||
LOG(ERROR) << "Failed to resolve service '" << name << "': " << avahi_strerror(avahi_client_errno(browseAvahi->client_)) << "\n";
|
LOG(ERROR) << "Failed to resolve service '" << name << "': " << avahi_strerror(avahi_client_errno(browseAvahi->client_)) << "\n";
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -166,12 +150,12 @@ void BrowseAvahi::browse_callback(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void BrowseAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata)
|
void BrowseAvahi::client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void* userdata)
|
||||||
{
|
{
|
||||||
assert(c);
|
assert(c);
|
||||||
|
|
||||||
/* Called whenever the client or server state changes */
|
/* Called whenever the client or server state changes */
|
||||||
// BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata);
|
// BrowseAvahi* browseAvahi = static_cast<BrowseAvahi*>(userdata);
|
||||||
|
|
||||||
if (state == AVAHI_CLIENT_FAILURE)
|
if (state == AVAHI_CLIENT_FAILURE)
|
||||||
{
|
{
|
||||||
|
@ -195,7 +179,8 @@ bool BrowseAvahi::browse(const std::string& serviceName, mDNSResult& result, int
|
||||||
throw SnapException("BrowseAvahi - Failed to create client: " + std::string(avahi_strerror(error)));
|
throw SnapException("BrowseAvahi - Failed to create client: " + std::string(avahi_strerror(error)));
|
||||||
|
|
||||||
/* Create the service browser */
|
/* Create the service browser */
|
||||||
if (!(sb_ = avahi_service_browser_new(client_, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, serviceName.c_str(), NULL, (AvahiLookupFlags)0, browse_callback, this)))
|
if (!(sb_ =
|
||||||
|
avahi_service_browser_new(client_, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, serviceName.c_str(), NULL, (AvahiLookupFlags)0, browse_callback, this)))
|
||||||
throw SnapException("BrowseAvahi - Failed to create service browser: " + std::string(avahi_strerror(avahi_client_errno(client_))));
|
throw SnapException("BrowseAvahi - Failed to create service browser: " + std::string(avahi_strerror(avahi_client_errno(client_))));
|
||||||
|
|
||||||
result_.valid = false;
|
result_.valid = false;
|
||||||
|
@ -220,5 +205,3 @@ bool BrowseAvahi::browse(const std::string& serviceName, mDNSResult& result, int
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
16
client/browseZeroConf/browseAvahi.h
Executable file → Normal file
16
client/browseZeroConf/browseAvahi.h
Executable file → Normal file
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -21,9 +21,9 @@
|
||||||
#include <avahi-client/client.h>
|
#include <avahi-client/client.h>
|
||||||
#include <avahi-client/lookup.h>
|
#include <avahi-client/lookup.h>
|
||||||
|
|
||||||
#include <avahi-common/simple-watch.h>
|
|
||||||
#include <avahi-common/malloc.h>
|
|
||||||
#include <avahi-common/error.h>
|
#include <avahi-common/error.h>
|
||||||
|
#include <avahi-common/malloc.h>
|
||||||
|
#include <avahi-common/simple-watch.h>
|
||||||
|
|
||||||
class BrowseAvahi;
|
class BrowseAvahi;
|
||||||
|
|
||||||
|
@ -38,9 +38,13 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void cleanUp();
|
void cleanUp();
|
||||||
static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, AVAHI_GCC_UNUSED void* userdata);
|
static void resolve_callback(AvahiServiceResolver* r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol,
|
||||||
static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata);
|
AvahiResolverEvent event, const char* name, const char* type, const char* domain, const char* host_name,
|
||||||
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata);
|
const AvahiAddress* address, uint16_t port, AvahiStringList* txt, AvahiLookupResultFlags flags,
|
||||||
|
AVAHI_GCC_UNUSED void* userdata);
|
||||||
|
static void browse_callback(AvahiServiceBrowser* b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char* name,
|
||||||
|
const char* type, const char* domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata);
|
||||||
|
static void client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void* userdata);
|
||||||
AvahiClient* client_;
|
AvahiClient* client_;
|
||||||
mDNSResult result_;
|
mDNSResult result_;
|
||||||
AvahiServiceBrowser* sb_;
|
AvahiServiceBrowser* sb_;
|
||||||
|
|
73
client/browseZeroConf/browseBonjour.cpp
Executable file → Normal file
73
client/browseZeroConf/browseBonjour.cpp
Executable file → Normal file
|
@ -1,14 +1,14 @@
|
||||||
#include "browseBonjour.h"
|
#include "browseBonjour.h"
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <iostream>
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#ifdef WINDOWS
|
#ifdef WINDOWS
|
||||||
#include <WinSock2.h>
|
#include <WinSock2.h>
|
||||||
#include <Ws2tcpip.h>
|
#include <Ws2tcpip.h>
|
||||||
#else
|
#else
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
@ -18,7 +18,7 @@ using namespace std;
|
||||||
|
|
||||||
struct DNSServiceRefDeleter
|
struct DNSServiceRefDeleter
|
||||||
{
|
{
|
||||||
void operator () (DNSServiceRef* ref)
|
void operator()(DNSServiceRef* ref)
|
||||||
{
|
{
|
||||||
DNSServiceRefDeallocate(*ref);
|
DNSServiceRefDeallocate(*ref);
|
||||||
delete ref;
|
delete ref;
|
||||||
|
@ -141,7 +141,9 @@ struct mDNSResolve
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define CHECKED(err) if((err)!=kDNSServiceErr_NoError)throw SnapException(BonjourGetError(err) + ":" + to_string(__LINE__));
|
#define CHECKED(err) \
|
||||||
|
if ((err) != kDNSServiceErr_NoError) \
|
||||||
|
throw SnapException(BonjourGetError(err) + ":" + to_string(__LINE__));
|
||||||
|
|
||||||
void runService(const DNSServiceHandle& service)
|
void runService(const DNSServiceHandle& service)
|
||||||
{
|
{
|
||||||
|
@ -173,20 +175,14 @@ bool BrowseBonjour::browse(const string& serviceName, mDNSResult& result, int ti
|
||||||
{
|
{
|
||||||
DNSServiceHandle service(new DNSServiceRef(NULL));
|
DNSServiceHandle service(new DNSServiceRef(NULL));
|
||||||
CHECKED(DNSServiceBrowse(service.get(), 0, 0, serviceName.c_str(), "local.",
|
CHECKED(DNSServiceBrowse(service.get(), 0, 0, serviceName.c_str(), "local.",
|
||||||
[](DNSServiceRef service,
|
[](DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
|
||||||
DNSServiceFlags flags,
|
const char* serviceName, const char* regtype, const char* replyDomain, void* context) {
|
||||||
uint32_t interfaceIndex,
|
|
||||||
DNSServiceErrorType errorCode,
|
|
||||||
const char* serviceName,
|
|
||||||
const char* regtype,
|
|
||||||
const char* replyDomain,
|
|
||||||
void* context)
|
|
||||||
{
|
|
||||||
auto replyCollection = static_cast<deque<mDNSReply>*>(context);
|
auto replyCollection = static_cast<deque<mDNSReply>*>(context);
|
||||||
|
|
||||||
CHECKED(errorCode);
|
CHECKED(errorCode);
|
||||||
replyCollection->push_back(mDNSReply{ string(serviceName), string(regtype), string(replyDomain) });
|
replyCollection->push_back(mDNSReply{string(serviceName), string(regtype), string(replyDomain)});
|
||||||
}, &replyCollection));
|
},
|
||||||
|
&replyCollection));
|
||||||
|
|
||||||
runService(service);
|
runService(service);
|
||||||
}
|
}
|
||||||
|
@ -196,29 +192,22 @@ bool BrowseBonjour::browse(const string& serviceName, mDNSResult& result, int ti
|
||||||
{
|
{
|
||||||
DNSServiceHandle service(new DNSServiceRef(NULL));
|
DNSServiceHandle service(new DNSServiceRef(NULL));
|
||||||
for (auto& reply : replyCollection)
|
for (auto& reply : replyCollection)
|
||||||
CHECKED(DNSServiceResolve(service.get(), 0, 0, reply.name.c_str(), reply.regtype.c_str(), reply.domain.c_str(),
|
CHECKED(
|
||||||
[](DNSServiceRef service,
|
DNSServiceResolve(service.get(), 0, 0, reply.name.c_str(), reply.regtype.c_str(), reply.domain.c_str(),
|
||||||
DNSServiceFlags flags,
|
[](DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char* fullName,
|
||||||
uint32_t interfaceIndex,
|
const char* hosttarget, uint16_t port, uint16_t txtLen, const unsigned char* txtRecord, void* context) {
|
||||||
DNSServiceErrorType errorCode,
|
|
||||||
const char* fullName,
|
|
||||||
const char* hosttarget,
|
|
||||||
uint16_t port,
|
|
||||||
uint16_t txtLen,
|
|
||||||
const unsigned char* txtRecord,
|
|
||||||
void* context)
|
|
||||||
{
|
|
||||||
auto resultCollection = static_cast<deque<mDNSResolve>*>(context);
|
auto resultCollection = static_cast<deque<mDNSResolve>*>(context);
|
||||||
|
|
||||||
CHECKED(errorCode);
|
CHECKED(errorCode);
|
||||||
resultCollection->push_back(mDNSResolve { string(hosttarget), ntohs(port) });
|
resultCollection->push_back(mDNSResolve{string(hosttarget), ntohs(port)});
|
||||||
}, &resolveCollection));
|
},
|
||||||
|
&resolveCollection));
|
||||||
|
|
||||||
runService(service);
|
runService(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS/mDNS Resolve
|
// DNS/mDNS Resolve
|
||||||
deque<mDNSResult> resultCollection(resolveCollection.size(), mDNSResult { IPVersion::IPv4, 0, "", "", 0, false });
|
deque<mDNSResult> resultCollection(resolveCollection.size(), mDNSResult{IPVersion::IPv4, 0, "", "", 0, false});
|
||||||
{
|
{
|
||||||
DNSServiceHandle service(new DNSServiceRef(NULL));
|
DNSServiceHandle service(new DNSServiceRef(NULL));
|
||||||
unsigned i = 0;
|
unsigned i = 0;
|
||||||
|
@ -226,32 +215,24 @@ bool BrowseBonjour::browse(const string& serviceName, mDNSResult& result, int ti
|
||||||
{
|
{
|
||||||
resultCollection[i].port = resolve.port;
|
resultCollection[i].port = resolve.port;
|
||||||
CHECKED(DNSServiceGetAddrInfo(service.get(), kDNSServiceFlagsLongLivedQuery, 0, kDNSServiceProtocol_IPv4, resolve.fullName.c_str(),
|
CHECKED(DNSServiceGetAddrInfo(service.get(), kDNSServiceFlagsLongLivedQuery, 0, kDNSServiceProtocol_IPv4, resolve.fullName.c_str(),
|
||||||
[](DNSServiceRef service,
|
[](DNSServiceRef service, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
|
||||||
DNSServiceFlags flags,
|
const char* hostname, const sockaddr* address, uint32_t ttl, void* context) {
|
||||||
uint32_t interfaceIndex,
|
|
||||||
DNSServiceErrorType errorCode,
|
|
||||||
const char* hostname,
|
|
||||||
const sockaddr* address,
|
|
||||||
uint32_t ttl,
|
|
||||||
void* context)
|
|
||||||
{
|
|
||||||
auto result = static_cast<mDNSResult*>(context);
|
auto result = static_cast<mDNSResult*>(context);
|
||||||
|
|
||||||
result->host = string(hostname);
|
result->host = string(hostname);
|
||||||
result->ip_version = (address->sa_family == AF_INET)?(IPVersion::IPv4):(IPVersion::IPv6);
|
result->ip_version = (address->sa_family == AF_INET) ? (IPVersion::IPv4) : (IPVersion::IPv6);
|
||||||
result->iface_idx = static_cast<int>(interfaceIndex);
|
result->iface_idx = static_cast<int>(interfaceIndex);
|
||||||
|
|
||||||
char hostIP[NI_MAXHOST];
|
char hostIP[NI_MAXHOST];
|
||||||
char hostService[NI_MAXSERV];
|
char hostService[NI_MAXSERV];
|
||||||
if (getnameinfo(address, sizeof(*address),
|
if (getnameinfo(address, sizeof(*address), hostIP, sizeof(hostIP), hostService, sizeof(hostService),
|
||||||
hostIP, sizeof(hostIP),
|
NI_NUMERICHOST | NI_NUMERICSERV) == 0)
|
||||||
hostService, sizeof(hostService),
|
|
||||||
NI_NUMERICHOST|NI_NUMERICSERV) == 0)
|
|
||||||
result->ip = string(hostIP);
|
result->ip = string(hostIP);
|
||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
result->valid = true;
|
result->valid = true;
|
||||||
}, &resultCollection[i++]));
|
},
|
||||||
|
&resultCollection[i++]));
|
||||||
}
|
}
|
||||||
runService(service);
|
runService(service);
|
||||||
}
|
}
|
||||||
|
|
0
client/browseZeroConf/browseBonjour.h
Executable file → Normal file
0
client/browseZeroConf/browseBonjour.h
Executable file → Normal file
0
client/browseZeroConf/browsemDNS.h
Executable file → Normal file
0
client/browseZeroConf/browsemDNS.h
Executable file → Normal file
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,19 +16,21 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include "clientConnection.h"
|
||||||
|
#include "aixlog.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
#include "common/strCompat.h"
|
||||||
|
#include "message/hello.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include "clientConnection.h"
|
|
||||||
#include "common/strCompat.h"
|
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "message/hello.h"
|
|
||||||
#include "aixlog.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
ClientConnection::ClientConnection(MessageReceiver* receiver, const std::string& host, size_t port) : socket_(nullptr), active_(false), connected_(false), messageReceiver_(receiver), reqId_(1), host_(host), port_(port), readerThread_(NULL), sumTimeout_(chronos::msec(0))
|
ClientConnection::ClientConnection(MessageReceiver* receiver, const std::string& host, size_t port)
|
||||||
|
: socket_(nullptr), active_(false), connected_(false), messageReceiver_(receiver), reqId_(1), host_(host), port_(port), readerThread_(NULL),
|
||||||
|
sumTimeout_(chronos::msec(0))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,10 +49,9 @@ void ClientConnection::socketRead(void* _to, size_t _bytes)
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
len += socket_->read_some(asio::buffer((char*)_to + len, toRead));
|
len += socket_->read_some(asio::buffer((char*)_to + len, toRead));
|
||||||
//cout << "len: " << len << ", error: " << error << endl;
|
// cout << "len: " << len << ", error: " << error << endl;
|
||||||
toRead = _bytes - len;
|
toRead = _bytes - len;
|
||||||
}
|
} while (toRead > 0);
|
||||||
while (toRead > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,12 +75,12 @@ void ClientConnection::start()
|
||||||
auto iterator = resolver.resolve(query);
|
auto iterator = resolver.resolve(query);
|
||||||
LOG(DEBUG) << "Connecting\n";
|
LOG(DEBUG) << "Connecting\n";
|
||||||
socket_.reset(new tcp::socket(io_service_));
|
socket_.reset(new tcp::socket(io_service_));
|
||||||
// struct timeval tv;
|
// struct timeval tv;
|
||||||
// tv.tv_sec = 5;
|
// tv.tv_sec = 5;
|
||||||
// tv.tv_usec = 0;
|
// tv.tv_usec = 0;
|
||||||
// cout << "socket: " << socket->native_handle() << "\n";
|
// cout << "socket: " << socket->native_handle() << "\n";
|
||||||
// setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
// setsockopt(socket->native_handle(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||||
// setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
// setsockopt(socket->native_handle(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||||
socket_->connect(*iterator);
|
socket_->connect(*iterator);
|
||||||
connected_ = true;
|
connected_ = true;
|
||||||
SLOG(NOTICE) << "Connected to " << socket_->remote_endpoint().address().to_string() << endl;
|
SLOG(NOTICE) << "Connected to " << socket_->remote_endpoint().address().to_string() << endl;
|
||||||
|
@ -99,9 +100,11 @@ void ClientConnection::stop()
|
||||||
if (socket_)
|
if (socket_)
|
||||||
{
|
{
|
||||||
socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
||||||
if (ec) LOG(ERROR) << "Error in socket shutdown: " << ec.message() << endl;
|
if (ec)
|
||||||
|
LOG(ERROR) << "Error in socket shutdown: " << ec.message() << endl;
|
||||||
socket_->close(ec);
|
socket_->close(ec);
|
||||||
if (ec) LOG(ERROR) << "Error in socket close: " << ec.message() << endl;
|
if (ec)
|
||||||
|
LOG(ERROR) << "Error in socket close: " << ec.message() << endl;
|
||||||
}
|
}
|
||||||
if (readerThread_)
|
if (readerThread_)
|
||||||
{
|
{
|
||||||
|
@ -110,7 +113,7 @@ void ClientConnection::stop()
|
||||||
delete readerThread_;
|
delete readerThread_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(...)
|
catch (...)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
readerThread_ = NULL;
|
readerThread_ = NULL;
|
||||||
|
@ -121,12 +124,12 @@ void ClientConnection::stop()
|
||||||
|
|
||||||
bool ClientConnection::send(const msg::BaseMessage* message) const
|
bool ClientConnection::send(const msg::BaseMessage* message) const
|
||||||
{
|
{
|
||||||
// std::unique_lock<std::mutex> mlock(mutex_);
|
// std::unique_lock<std::mutex> mlock(mutex_);
|
||||||
//LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n";
|
// LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n";
|
||||||
std::lock_guard<std::mutex> socketLock(socketMutex_);
|
std::lock_guard<std::mutex> socketLock(socketMutex_);
|
||||||
if (!connected())
|
if (!connected())
|
||||||
return false;
|
return false;
|
||||||
//LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n";
|
// LOG(DEBUG) << "send: " << message->type << ", size: " << message->getSize() << "\n";
|
||||||
asio::streambuf streambuf;
|
asio::streambuf streambuf;
|
||||||
std::ostream stream(&streambuf);
|
std::ostream stream(&streambuf);
|
||||||
tv t;
|
tv t;
|
||||||
|
@ -143,7 +146,7 @@ shared_ptr<msg::SerializedMessage> ClientConnection::sendRequest(const msg::Base
|
||||||
if (++reqId_ >= 10000)
|
if (++reqId_ >= 10000)
|
||||||
reqId_ = 1;
|
reqId_ = 1;
|
||||||
message->id = reqId_;
|
message->id = reqId_;
|
||||||
// LOG(INFO) << "Req: " << message->id << "\n";
|
// LOG(INFO) << "Req: " << message->id << "\n";
|
||||||
shared_ptr<PendingRequest> pendingRequest(new PendingRequest(reqId_));
|
shared_ptr<PendingRequest> pendingRequest(new PendingRequest(reqId_));
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(pendingRequestsMutex_);
|
std::unique_lock<std::mutex> lock(pendingRequestsMutex_);
|
||||||
|
@ -153,7 +156,7 @@ shared_ptr<msg::SerializedMessage> ClientConnection::sendRequest(const msg::Base
|
||||||
{
|
{
|
||||||
response = pendingRequest->response;
|
response = pendingRequest->response;
|
||||||
sumTimeout_ = chronos::msec(0);
|
sumTimeout_ = chronos::msec(0);
|
||||||
// LOG(INFO) << "Resp: " << pendingRequest->id << "\n";
|
// LOG(INFO) << "Resp: " << pendingRequest->id << "\n";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -174,21 +177,23 @@ void ClientConnection::getNextMessage()
|
||||||
vector<char> buffer(baseMsgSize);
|
vector<char> buffer(baseMsgSize);
|
||||||
socketRead(&buffer[0], baseMsgSize);
|
socketRead(&buffer[0], baseMsgSize);
|
||||||
baseMessage.deserialize(&buffer[0]);
|
baseMessage.deserialize(&buffer[0]);
|
||||||
// LOG(DEBUG) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << "\n";
|
// LOG(DEBUG) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " <<
|
||||||
|
//baseMessage.refersTo << "\n";
|
||||||
if (baseMessage.size > buffer.size())
|
if (baseMessage.size > buffer.size())
|
||||||
buffer.resize(baseMessage.size);
|
buffer.resize(baseMessage.size);
|
||||||
// {
|
// {
|
||||||
// std::lock_guard<std::mutex> socketLock(socketMutex_);
|
// std::lock_guard<std::mutex> socketLock(socketMutex_);
|
||||||
socketRead(&buffer[0], baseMessage.size);
|
socketRead(&buffer[0], baseMessage.size);
|
||||||
// }
|
// }
|
||||||
tv t;
|
tv t;
|
||||||
baseMessage.received = t;
|
baseMessage.received = t;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(pendingRequestsMutex_);
|
std::unique_lock<std::mutex> lock(pendingRequestsMutex_);
|
||||||
// LOG(DEBUG) << "got lock - getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << "\n";
|
// LOG(DEBUG) << "got lock - getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ",
|
||||||
|
//refers: " << baseMessage.refersTo << "\n";
|
||||||
{
|
{
|
||||||
for (auto req: pendingRequests_)
|
for (auto req : pendingRequests_)
|
||||||
{
|
{
|
||||||
if (req->id == baseMessage.refersTo)
|
if (req->id == baseMessage.refersTo)
|
||||||
{
|
{
|
||||||
|
@ -214,7 +219,7 @@ void ClientConnection::reader()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while(active_)
|
while (active_)
|
||||||
{
|
{
|
||||||
getNextMessage();
|
getNextMessage();
|
||||||
}
|
}
|
||||||
|
@ -230,7 +235,3 @@ void ClientConnection::reader()
|
||||||
connected_ = false;
|
connected_ = false;
|
||||||
active_ = false;
|
active_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,16 +19,16 @@
|
||||||
#ifndef CLIENT_CONNECTION_H
|
#ifndef CLIENT_CONNECTION_H
|
||||||
#define CLIENT_CONNECTION_H
|
#define CLIENT_CONNECTION_H
|
||||||
|
|
||||||
|
#include "common/timeDefs.h"
|
||||||
|
#include "message/message.h"
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
|
||||||
#include <mutex>
|
|
||||||
#include <memory>
|
|
||||||
#include <asio.hpp>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <set>
|
|
||||||
#include "message/message.h"
|
|
||||||
#include "common/timeDefs.h"
|
|
||||||
|
|
||||||
|
|
||||||
using asio::ip::tcp;
|
using asio::ip::tcp;
|
||||||
|
@ -40,7 +40,7 @@ class ClientConnection;
|
||||||
/// Used to synchronize server requests (wait for server response)
|
/// Used to synchronize server requests (wait for server response)
|
||||||
struct PendingRequest
|
struct PendingRequest
|
||||||
{
|
{
|
||||||
PendingRequest(uint16_t reqId) : id(reqId), response(NULL) {};
|
PendingRequest(uint16_t reqId) : id(reqId), response(NULL){};
|
||||||
|
|
||||||
uint16_t id;
|
uint16_t id;
|
||||||
std::shared_ptr<msg::SerializedMessage> response;
|
std::shared_ptr<msg::SerializedMessage> response;
|
||||||
|
@ -103,7 +103,7 @@ public:
|
||||||
virtual bool connected() const
|
virtual bool connected() const
|
||||||
{
|
{
|
||||||
return (socket_ != nullptr);
|
return (socket_ != nullptr);
|
||||||
// return (connected_ && socket);
|
// return (connected_ && socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -130,7 +130,3 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,37 +16,29 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "decoder/pcmDecoder.h"
|
#include "decoder/pcmDecoder.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#if defined(HAS_OGG) && (defined(HAS_TREMOR) || defined(HAS_VORBIS))
|
#if defined(HAS_OGG) && (defined(HAS_TREMOR) || defined(HAS_VORBIS))
|
||||||
#include "decoder/oggDecoder.h"
|
#include "decoder/oggDecoder.h"
|
||||||
#endif
|
#endif
|
||||||
#if defined(HAS_FLAC)
|
#if defined(HAS_FLAC)
|
||||||
#include "decoder/flacDecoder.h"
|
#include "decoder/flacDecoder.h"
|
||||||
#endif
|
#endif
|
||||||
#include "timeProvider.h"
|
|
||||||
#include "message/time.h"
|
|
||||||
#include "message/hello.h"
|
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
#include "message/hello.h"
|
||||||
|
#include "message/time.h"
|
||||||
|
#include "timeProvider.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
Controller::Controller(const std::string& hostId, size_t instance, std::shared_ptr<MetadataAdapter> meta) : MessageReceiver(),
|
Controller::Controller(const std::string& hostId, size_t instance, std::shared_ptr<MetadataAdapter> meta)
|
||||||
hostId_(hostId),
|
: MessageReceiver(), hostId_(hostId), instance_(instance), active_(false), latency_(0), stream_(nullptr), decoder_(nullptr), player_(nullptr), meta_(meta),
|
||||||
instance_(instance),
|
serverSettings_(nullptr), async_exception_(nullptr)
|
||||||
active_(false),
|
|
||||||
latency_(0),
|
|
||||||
stream_(nullptr),
|
|
||||||
decoder_(nullptr),
|
|
||||||
player_(nullptr),
|
|
||||||
meta_(meta),
|
|
||||||
serverSettings_(nullptr),
|
|
||||||
async_exception_(nullptr)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,12 +59,13 @@ void Controller::onMessageReceived(ClientConnection* connection, const msg::Base
|
||||||
{
|
{
|
||||||
msg::PcmChunk* pcmChunk = new msg::PcmChunk(sampleFormat_, 0);
|
msg::PcmChunk* pcmChunk = new msg::PcmChunk(sampleFormat_, 0);
|
||||||
pcmChunk->deserialize(baseMessage, buffer);
|
pcmChunk->deserialize(baseMessage, buffer);
|
||||||
// LOG(DEBUG) << "chunk: " << pcmChunk->payloadSize << ", sampleFormat: " << sampleFormat_.rate << "\n";
|
// LOG(DEBUG) << "chunk: " << pcmChunk->payloadSize << ", sampleFormat: " << sampleFormat_.rate << "\n";
|
||||||
if (decoder_->decode(pcmChunk))
|
if (decoder_->decode(pcmChunk))
|
||||||
{
|
{
|
||||||
//TODO: do decoding in thread?
|
// TODO: do decoding in thread?
|
||||||
stream_->addChunk(pcmChunk);
|
stream_->addChunk(pcmChunk);
|
||||||
//LOG(DEBUG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->getDuration() << ", sec: " << pcmChunk->timestamp.sec << ", usec: " << pcmChunk->timestamp.usec/1000 << ", type: " << pcmChunk->type << "\n";
|
// LOG(DEBUG) << ", decoded: " << pcmChunk->payloadSize << ", Duration: " << pcmChunk->getDuration() << ", sec: " << pcmChunk->timestamp.sec <<
|
||||||
|
// ", usec: " << pcmChunk->timestamp.usec/1000 << ", type: " << pcmChunk->type << "\n";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
delete pcmChunk;
|
delete pcmChunk;
|
||||||
|
@ -82,13 +75,14 @@ void Controller::onMessageReceived(ClientConnection* connection, const msg::Base
|
||||||
{
|
{
|
||||||
msg::Time reply;
|
msg::Time reply;
|
||||||
reply.deserialize(baseMessage, buffer);
|
reply.deserialize(baseMessage, buffer);
|
||||||
TimeProvider::getInstance().setDiff(reply.latency, reply.received - reply.sent);// ToServer(diff / 2);
|
TimeProvider::getInstance().setDiff(reply.latency, reply.received - reply.sent); // ToServer(diff / 2);
|
||||||
}
|
}
|
||||||
else if (baseMessage.type == message_type::kServerSettings)
|
else if (baseMessage.type == message_type::kServerSettings)
|
||||||
{
|
{
|
||||||
serverSettings_.reset(new msg::ServerSettings());
|
serverSettings_.reset(new msg::ServerSettings());
|
||||||
serverSettings_->deserialize(baseMessage, buffer);
|
serverSettings_->deserialize(baseMessage, buffer);
|
||||||
LOG(INFO) << "ServerSettings - buffer: " << serverSettings_->getBufferMs() << ", latency: " << serverSettings_->getLatency() << ", volume: " << serverSettings_->getVolume() << ", muted: " << serverSettings_->isMuted() << "\n";
|
LOG(INFO) << "ServerSettings - buffer: " << serverSettings_->getBufferMs() << ", latency: " << serverSettings_->getLatency()
|
||||||
|
<< ", volume: " << serverSettings_->getVolume() << ", muted: " << serverSettings_->isMuted() << "\n";
|
||||||
if (stream_ && player_)
|
if (stream_ && player_)
|
||||||
{
|
{
|
||||||
player_->setVolume(serverSettings_->getVolume() / 100.);
|
player_->setVolume(serverSettings_->getVolume() / 100.);
|
||||||
|
@ -143,7 +137,7 @@ void Controller::onMessageReceived(ClientConnection* connection, const msg::Base
|
||||||
streamTags_.reset(new msg::StreamTags());
|
streamTags_.reset(new msg::StreamTags());
|
||||||
streamTags_->deserialize(baseMessage, buffer);
|
streamTags_->deserialize(baseMessage, buffer);
|
||||||
|
|
||||||
if(meta_)
|
if (meta_)
|
||||||
meta_->push(streamTags_->msg);
|
meta_->push(streamTags_->msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +199,7 @@ void Controller::worker()
|
||||||
|
|
||||||
/// Do initial time sync with the server
|
/// Do initial time sync with the server
|
||||||
msg::Time timeReq;
|
msg::Time timeReq;
|
||||||
for (size_t n=0; n<50 && active_; ++n)
|
for (size_t n = 0; n < 50 && active_; ++n)
|
||||||
{
|
{
|
||||||
if (async_exception_)
|
if (async_exception_)
|
||||||
{
|
{
|
||||||
|
@ -226,7 +220,7 @@ void Controller::worker()
|
||||||
while (active_)
|
while (active_)
|
||||||
{
|
{
|
||||||
LOG(DEBUG) << "Main loop\n";
|
LOG(DEBUG) << "Main loop\n";
|
||||||
for (size_t n=0; n<10 && active_; ++n)
|
for (size_t n = 0; n < 10 && active_; ++n)
|
||||||
{
|
{
|
||||||
chronos::sleep(100);
|
chronos::sleep(100);
|
||||||
if (async_exception_)
|
if (async_exception_)
|
||||||
|
@ -248,12 +242,9 @@ void Controller::worker()
|
||||||
player_.reset();
|
player_.reset();
|
||||||
stream_.reset();
|
stream_.reset();
|
||||||
decoder_.reset();
|
decoder_.reset();
|
||||||
for (size_t n=0; (n<10) && active_; ++n)
|
for (size_t n = 0; (n < 10) && active_; ++n)
|
||||||
chronos::sleep(100);
|
chronos::sleep(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG(DEBUG) << "Thread stopped\n";
|
LOG(DEBUG) << "Thread stopped\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,13 +19,13 @@
|
||||||
#ifndef CONTROLLER_H
|
#ifndef CONTROLLER_H
|
||||||
#define CONTROLLER_H
|
#define CONTROLLER_H
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
#include <atomic>
|
|
||||||
#include "decoder/decoder.h"
|
#include "decoder/decoder.h"
|
||||||
#include "message/message.h"
|
#include "message/message.h"
|
||||||
#include "message/serverSettings.h"
|
#include "message/serverSettings.h"
|
||||||
#include "message/streamTags.h"
|
#include "message/streamTags.h"
|
||||||
#include "player/pcmDevice.h"
|
#include "player/pcmDevice.h"
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
#ifdef HAS_ALSA
|
#ifdef HAS_ALSA
|
||||||
#include "player/alsaPlayer.h"
|
#include "player/alsaPlayer.h"
|
||||||
#elif HAS_OPENSL
|
#elif HAS_OPENSL
|
||||||
|
@ -34,8 +34,8 @@
|
||||||
#include "player/coreAudioPlayer.h"
|
#include "player/coreAudioPlayer.h"
|
||||||
#endif
|
#endif
|
||||||
#include "clientConnection.h"
|
#include "clientConnection.h"
|
||||||
#include "stream.h"
|
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
|
#include "stream.h"
|
||||||
|
|
||||||
|
|
||||||
/// Forwards PCM data to the audio player
|
/// Forwards PCM data to the audio player
|
||||||
|
@ -86,4 +86,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,17 +18,17 @@
|
||||||
|
|
||||||
#ifndef DECODER_H
|
#ifndef DECODER_H
|
||||||
#define DECODER_H
|
#define DECODER_H
|
||||||
#include <mutex>
|
|
||||||
#include "message/pcmChunk.h"
|
|
||||||
#include "message/codecHeader.h"
|
|
||||||
#include "common/sampleFormat.h"
|
#include "common/sampleFormat.h"
|
||||||
|
#include "message/codecHeader.h"
|
||||||
|
#include "message/pcmChunk.h"
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
|
||||||
class Decoder
|
class Decoder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Decoder() {};
|
Decoder(){};
|
||||||
virtual ~Decoder() {};
|
virtual ~Decoder(){};
|
||||||
virtual bool decode(msg::PcmChunk* chunk) = 0;
|
virtual bool decode(msg::PcmChunk* chunk) = 0;
|
||||||
virtual SampleFormat setHeader(msg::CodecHeader* chunk) = 0;
|
virtual SampleFormat setHeader(msg::CodecHeader* chunk) = 0;
|
||||||
|
|
||||||
|
@ -38,5 +38,3 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,29 +16,30 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cmath>
|
|
||||||
#include "flacDecoder.h"
|
#include "flacDecoder.h"
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "common/endian.hpp"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "common/endian.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
static FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data);
|
static FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder* decoder, FLAC__byte buffer[], size_t* bytes, void* client_data);
|
||||||
static FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data);
|
static FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder* decoder, const FLAC__Frame* frame, const FLAC__int32* const buffer[],
|
||||||
static void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data);
|
void* client_data);
|
||||||
static void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data);
|
static void metadata_callback(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data);
|
||||||
|
static void error_callback(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data);
|
||||||
|
|
||||||
|
|
||||||
static msg::CodecHeader* flacHeader = NULL;
|
static msg::CodecHeader* flacHeader = NULL;
|
||||||
static msg::PcmChunk* flacChunk = NULL;
|
static msg::PcmChunk* flacChunk = NULL;
|
||||||
static msg::PcmChunk* pcmChunk = NULL;
|
static msg::PcmChunk* pcmChunk = NULL;
|
||||||
static SampleFormat sampleFormat;
|
static SampleFormat sampleFormat;
|
||||||
static FLAC__StreamDecoder *decoder = NULL;
|
static FLAC__StreamDecoder* decoder = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ bool FlacDecoder::decode(msg::PcmChunk* chunk)
|
||||||
if (lastError_)
|
if (lastError_)
|
||||||
{
|
{
|
||||||
LOG(ERROR) << "FLAC decode error: " << FLAC__StreamDecoderErrorStatusString[*lastError_] << "\n";
|
LOG(ERROR) << "FLAC decode error: " << FLAC__StreamDecoderErrorStatusString[*lastError_] << "\n";
|
||||||
lastError_= nullptr;
|
lastError_ = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +104,7 @@ SampleFormat FlacDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
if ((decoder = FLAC__stream_decoder_new()) == NULL)
|
if ((decoder = FLAC__stream_decoder_new()) == NULL)
|
||||||
throw SnapException("ERROR: allocating decoder");
|
throw SnapException("ERROR: allocating decoder");
|
||||||
|
|
||||||
// (void)FLAC__stream_decoder_set_md5_checking(decoder, true);
|
// (void)FLAC__stream_decoder_set_md5_checking(decoder, true);
|
||||||
init_status = FLAC__stream_decoder_init_stream(decoder, read_callback, NULL, NULL, NULL, NULL, write_callback, metadata_callback, error_callback, this);
|
init_status = FLAC__stream_decoder_init_stream(decoder, read_callback, NULL, NULL, NULL, NULL, write_callback, metadata_callback, error_callback, this);
|
||||||
if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
|
if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
|
||||||
throw SnapException("ERROR: initializing decoder: " + string(FLAC__StreamDecoderInitStatusString[init_status]));
|
throw SnapException("ERROR: initializing decoder: " + string(FLAC__StreamDecoderInitStatusString[init_status]));
|
||||||
|
@ -117,7 +118,7 @@ SampleFormat FlacDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data)
|
FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder* decoder, FLAC__byte buffer[], size_t* bytes, void* client_data)
|
||||||
{
|
{
|
||||||
if (flacHeader != NULL)
|
if (flacHeader != NULL)
|
||||||
{
|
{
|
||||||
|
@ -127,13 +128,13 @@ FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder,
|
||||||
}
|
}
|
||||||
else if (flacChunk != NULL)
|
else if (flacChunk != NULL)
|
||||||
{
|
{
|
||||||
// cerr << "read_callback: " << *bytes << ", avail: " << flacChunk->payloadSize << "\n";
|
// cerr << "read_callback: " << *bytes << ", avail: " << flacChunk->payloadSize << "\n";
|
||||||
static_cast<FlacDecoder*>(client_data)->cacheInfo_.isCachedChunk_ = false;
|
static_cast<FlacDecoder*>(client_data)->cacheInfo_.isCachedChunk_ = false;
|
||||||
if (*bytes > flacChunk->payloadSize)
|
if (*bytes > flacChunk->payloadSize)
|
||||||
*bytes = flacChunk->payloadSize;
|
*bytes = flacChunk->payloadSize;
|
||||||
|
|
||||||
// if (*bytes == 0)
|
// if (*bytes == 0)
|
||||||
// return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
// return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
||||||
|
|
||||||
memcpy(buffer, flacChunk->payload, *bytes);
|
memcpy(buffer, flacChunk->payload, *bytes);
|
||||||
memmove(flacChunk->payload, flacChunk->payload + *bytes, flacChunk->payloadSize - *bytes);
|
memmove(flacChunk->payload, flacChunk->payload + *bytes, flacChunk->payloadSize - *bytes);
|
||||||
|
@ -144,7 +145,8 @@ FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder *decoder,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
|
FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder* decoder, const FLAC__Frame* frame, const FLAC__int32* const buffer[],
|
||||||
|
void* client_data)
|
||||||
{
|
{
|
||||||
(void)decoder;
|
(void)decoder;
|
||||||
|
|
||||||
|
@ -170,19 +172,19 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
|
||||||
{
|
{
|
||||||
int8_t* chunkBuffer = (int8_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
int8_t* chunkBuffer = (int8_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
||||||
for (size_t i = 0; i < frame->header.blocksize; i++)
|
for (size_t i = 0; i < frame->header.blocksize; i++)
|
||||||
chunkBuffer[sampleFormat.channels*i + channel] = (int8_t)(buffer[channel][i]);
|
chunkBuffer[sampleFormat.channels * i + channel] = (int8_t)(buffer[channel][i]);
|
||||||
}
|
}
|
||||||
else if (sampleFormat.sampleSize == 2)
|
else if (sampleFormat.sampleSize == 2)
|
||||||
{
|
{
|
||||||
int16_t* chunkBuffer = (int16_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
int16_t* chunkBuffer = (int16_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
||||||
for (size_t i = 0; i < frame->header.blocksize; i++)
|
for (size_t i = 0; i < frame->header.blocksize; i++)
|
||||||
chunkBuffer[sampleFormat.channels*i + channel] = SWAP_16((int16_t)(buffer[channel][i]));
|
chunkBuffer[sampleFormat.channels * i + channel] = SWAP_16((int16_t)(buffer[channel][i]));
|
||||||
}
|
}
|
||||||
else if (sampleFormat.sampleSize == 4)
|
else if (sampleFormat.sampleSize == 4)
|
||||||
{
|
{
|
||||||
int32_t* chunkBuffer = (int32_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
int32_t* chunkBuffer = (int32_t*)(pcmChunk->payload + pcmChunk->payloadSize);
|
||||||
for (size_t i = 0; i < frame->header.blocksize; i++)
|
for (size_t i = 0; i < frame->header.blocksize; i++)
|
||||||
chunkBuffer[sampleFormat.channels*i + channel] = SWAP_32((int32_t)(buffer[channel][i]));
|
chunkBuffer[sampleFormat.channels * i + channel] = SWAP_32((int32_t)(buffer[channel][i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pcmChunk->payloadSize += bytes;
|
pcmChunk->payloadSize += bytes;
|
||||||
|
@ -192,22 +194,19 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
|
void metadata_callback(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data)
|
||||||
{
|
{
|
||||||
(void)decoder;
|
(void)decoder;
|
||||||
/* print some stats */
|
/* print some stats */
|
||||||
if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO)
|
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO)
|
||||||
{
|
{
|
||||||
static_cast<FlacDecoder*>(client_data)->cacheInfo_.sampleRate_ = metadata->data.stream_info.sample_rate;
|
static_cast<FlacDecoder*>(client_data)->cacheInfo_.sampleRate_ = metadata->data.stream_info.sample_rate;
|
||||||
sampleFormat.setFormat(
|
sampleFormat.setFormat(metadata->data.stream_info.sample_rate, metadata->data.stream_info.bits_per_sample, metadata->data.stream_info.channels);
|
||||||
metadata->data.stream_info.sample_rate,
|
|
||||||
metadata->data.stream_info.bits_per_sample,
|
|
||||||
metadata->data.stream_info.channels);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
|
void error_callback(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data)
|
||||||
{
|
{
|
||||||
(void)decoder, (void)client_data;
|
(void)decoder, (void)client_data;
|
||||||
SLOG(ERROR) << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << "\n";
|
SLOG(ERROR) << "Got error callback: " << FLAC__StreamDecoderErrorStatusString[status] << "\n";
|
||||||
|
@ -223,8 +222,3 @@ void error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderError
|
||||||
// Oct 27 17:47:13 kitchen snapclient[869]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM
|
// Oct 27 17:47:13 kitchen snapclient[869]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM
|
||||||
// Oct 27 17:47:13 kitchen snapclient[869]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC
|
// Oct 27 17:47:13 kitchen snapclient[869]: Got error callback: FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -60,5 +60,3 @@ public:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,14 +16,14 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "oggDecoder.h"
|
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "common/endian.hpp"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "common/endian.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
#include "oggDecoder.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -55,7 +55,7 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
stream initial header) We need the first page to get the stream
|
stream initial header) We need the first page to get the stream
|
||||||
serialno. */
|
serialno. */
|
||||||
int size = chunk->payloadSize;
|
int size = chunk->payloadSize;
|
||||||
char *buffer = ogg_sync_buffer(&oy, size);
|
char* buffer = ogg_sync_buffer(&oy, size);
|
||||||
memcpy(buffer, chunk->payload, size);
|
memcpy(buffer, chunk->payload, size);
|
||||||
ogg_sync_wrote(&oy, size);
|
ogg_sync_wrote(&oy, size);
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
chunk->payloadSize = 0;
|
chunk->payloadSize = 0;
|
||||||
/* The rest is just a straight decode loop until end of stream */
|
/* The rest is just a straight decode loop until end of stream */
|
||||||
// while(!eos){
|
// while(!eos){
|
||||||
while(true)
|
while (true)
|
||||||
{
|
{
|
||||||
int result = ogg_sync_pageout(&oy, &og);
|
int result = ogg_sync_pageout(&oy, &og);
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
|
@ -75,8 +75,8 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ogg_stream_pagein(&os,&og); /* can safely ignore errors at this point */
|
ogg_stream_pagein(&os, &og); /* can safely ignore errors at this point */
|
||||||
while(1)
|
while (1)
|
||||||
{
|
{
|
||||||
result = ogg_stream_packetout(&os, &op);
|
result = ogg_stream_packetout(&os, &op);
|
||||||
|
|
||||||
|
@ -87,13 +87,13 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
/* no reason to complain; already complained above */
|
/* no reason to complain; already complained above */
|
||||||
/* we have a packet. Decode it */
|
/* we have a packet. Decode it */
|
||||||
#ifdef HAS_TREMOR
|
#ifdef HAS_TREMOR
|
||||||
ogg_int32_t **pcm;
|
ogg_int32_t** pcm;
|
||||||
#else
|
#else
|
||||||
float **pcm;
|
float** pcm;
|
||||||
#endif
|
#endif
|
||||||
int samples;
|
int samples;
|
||||||
|
|
||||||
if (vorbis_synthesis(&vb,&op) == 0) /* test for success! */
|
if (vorbis_synthesis(&vb, &op) == 0) /* test for success! */
|
||||||
vorbis_synthesis_blockin(&vd, &vb);
|
vorbis_synthesis_blockin(&vd, &vb);
|
||||||
/*
|
/*
|
||||||
**pcm is a multichannel float vector. In stereo, for
|
**pcm is a multichannel float vector. In stereo, for
|
||||||
|
@ -111,11 +111,11 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
int8_t* chunkBuffer = (int8_t*)(chunk->payload + chunk->payloadSize);
|
int8_t* chunkBuffer = (int8_t*)(chunk->payload + chunk->payloadSize);
|
||||||
for (int i = 0; i < samples; i++)
|
for (int i = 0; i < samples; i++)
|
||||||
{
|
{
|
||||||
int8_t& val = chunkBuffer[sampleFormat_.channels*i + channel];
|
int8_t& val = chunkBuffer[sampleFormat_.channels * i + channel];
|
||||||
#ifdef HAS_TREMOR
|
#ifdef HAS_TREMOR
|
||||||
val = clip<int8_t>(pcm[channel][i], -128, 127);
|
val = clip<int8_t>(pcm[channel][i], -128, 127);
|
||||||
#else
|
#else
|
||||||
val = clip<int8_t>(floor(pcm[channel][i]*127.f + .5f), -128, 127);
|
val = clip<int8_t>(floor(pcm[channel][i] * 127.f + .5f), -128, 127);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,11 +124,11 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
int16_t* chunkBuffer = (int16_t*)(chunk->payload + chunk->payloadSize);
|
int16_t* chunkBuffer = (int16_t*)(chunk->payload + chunk->payloadSize);
|
||||||
for (int i = 0; i < samples; i++)
|
for (int i = 0; i < samples; i++)
|
||||||
{
|
{
|
||||||
int16_t& val = chunkBuffer[sampleFormat_.channels*i + channel];
|
int16_t& val = chunkBuffer[sampleFormat_.channels * i + channel];
|
||||||
#ifdef HAS_TREMOR
|
#ifdef HAS_TREMOR
|
||||||
val = SWAP_16(clip<int16_t>(pcm[channel][i] >> 9, -32768, 32767));
|
val = SWAP_16(clip<int16_t>(pcm[channel][i] >> 9, -32768, 32767));
|
||||||
#else
|
#else
|
||||||
val = SWAP_16(clip<int16_t>(floor(pcm[channel][i]*32767.f + .5f), -32768, 32767));
|
val = SWAP_16(clip<int16_t>(floor(pcm[channel][i] * 32767.f + .5f), -32768, 32767));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,11 +137,11 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
int32_t* chunkBuffer = (int32_t*)(chunk->payload + chunk->payloadSize);
|
int32_t* chunkBuffer = (int32_t*)(chunk->payload + chunk->payloadSize);
|
||||||
for (int i = 0; i < samples; i++)
|
for (int i = 0; i < samples; i++)
|
||||||
{
|
{
|
||||||
int32_t& val = chunkBuffer[sampleFormat_.channels*i + channel];
|
int32_t& val = chunkBuffer[sampleFormat_.channels * i + channel];
|
||||||
#ifdef HAS_TREMOR
|
#ifdef HAS_TREMOR
|
||||||
val = SWAP_32(clip<int32_t>(pcm[channel][i] << 7, -2147483648, 2147483647));
|
val = SWAP_32(clip<int32_t>(pcm[channel][i] << 7, -2147483648, 2147483647));
|
||||||
#else
|
#else
|
||||||
val = SWAP_32(clip<int32_t>(floor(pcm[channel][i]*2147483647.f + .5f), -2147483648, 2147483647));
|
val = SWAP_32(clip<int32_t>(floor(pcm[channel][i] * 2147483647.f + .5f), -2147483648, 2147483647));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,14 +160,14 @@ bool OggDecoder::decode(msg::PcmChunk* chunk)
|
||||||
SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
{
|
{
|
||||||
int size = chunk->payloadSize;
|
int size = chunk->payloadSize;
|
||||||
char *buffer = ogg_sync_buffer(&oy, size);
|
char* buffer = ogg_sync_buffer(&oy, size);
|
||||||
memcpy(buffer, chunk->payload, size);
|
memcpy(buffer, chunk->payload, size);
|
||||||
ogg_sync_wrote(&oy, size);
|
ogg_sync_wrote(&oy, size);
|
||||||
|
|
||||||
if (ogg_sync_pageout(&oy, &og) != 1)
|
if (ogg_sync_pageout(&oy, &og) != 1)
|
||||||
throw SnapException("Input does not appear to be an Ogg bitstream");
|
throw SnapException("Input does not appear to be an Ogg bitstream");
|
||||||
|
|
||||||
ogg_stream_init(&os,ogg_page_serialno(&og));
|
ogg_stream_init(&os, ogg_page_serialno(&og));
|
||||||
|
|
||||||
vorbis_info_init(&vi);
|
vorbis_info_init(&vi);
|
||||||
vorbis_comment_init(&vc);
|
vorbis_comment_init(&vc);
|
||||||
|
@ -185,7 +185,7 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
{
|
{
|
||||||
while (i < 2)
|
while (i < 2)
|
||||||
{
|
{
|
||||||
int result=ogg_sync_pageout(&oy, &og);
|
int result = ogg_sync_pageout(&oy, &og);
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
break; /* Need more data */
|
break; /* Need more data */
|
||||||
/* Don't complain about missing or corrupt data yet. We'll
|
/* Don't complain about missing or corrupt data yet. We'll
|
||||||
|
@ -195,7 +195,7 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
ogg_stream_pagein(&os, &og); /* we can ignore any errors here as they'll also become apparent at packetout */
|
ogg_stream_pagein(&os, &og); /* we can ignore any errors here as they'll also become apparent at packetout */
|
||||||
while (i < 2)
|
while (i < 2)
|
||||||
{
|
{
|
||||||
result=ogg_stream_packetout(&os, &op);
|
result = ogg_stream_packetout(&os, &op);
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
break;
|
break;
|
||||||
/// Uh oh; data at some point was corrupted or missing!
|
/// Uh oh; data at some point was corrupted or missing!
|
||||||
|
@ -203,7 +203,7 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
throw SnapException("Corrupt secondary header. Exiting.");
|
throw SnapException("Corrupt secondary header. Exiting.");
|
||||||
|
|
||||||
result=vorbis_synthesis_headerin(&vi, &vc, &op);
|
result = vorbis_synthesis_headerin(&vi, &vc, &op);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
throw SnapException("Corrupt secondary header. Exiting.");
|
throw SnapException("Corrupt secondary header. Exiting.");
|
||||||
|
|
||||||
|
@ -223,13 +223,14 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
sampleFormat_.setFormat(vi.rate, 16, vi.channels);
|
sampleFormat_.setFormat(vi.rate, 16, vi.channels);
|
||||||
|
|
||||||
/* Throw the comments plus a few lines about the bitstream we're decoding */
|
/* Throw the comments plus a few lines about the bitstream we're decoding */
|
||||||
char **ptr=vc.user_comments;
|
char** ptr = vc.user_comments;
|
||||||
while (*ptr)
|
while (*ptr)
|
||||||
{
|
{
|
||||||
std::string comment(*ptr);
|
std::string comment(*ptr);
|
||||||
if (comment.find("SAMPLE_FORMAT=") == 0)
|
if (comment.find("SAMPLE_FORMAT=") == 0)
|
||||||
sampleFormat_.setFormat(comment.substr(comment.find("=") + 1));
|
sampleFormat_.setFormat(comment.substr(comment.find("=") + 1));
|
||||||
LOG(INFO) << "comment: " << comment << "\n";;
|
LOG(INFO) << "comment: " << comment << "\n";
|
||||||
|
;
|
||||||
++ptr;
|
++ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,6 +238,3 @@ SampleFormat OggDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
|
|
||||||
return sampleFormat_;
|
return sampleFormat_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -39,8 +39,10 @@ private:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T clip(const T& value, const T& lower, const T& upper) const
|
T clip(const T& value, const T& lower, const T& upper) const
|
||||||
{
|
{
|
||||||
if (value > upper) return upper;
|
if (value > upper)
|
||||||
if (value < lower) return lower;
|
return upper;
|
||||||
|
if (value < lower)
|
||||||
|
return lower;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,5 +61,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,10 +16,10 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "common/endian.hpp"
|
|
||||||
#include "aixlog.hpp"
|
|
||||||
#include "pcmDecoder.h"
|
#include "pcmDecoder.h"
|
||||||
|
#include "aixlog.hpp"
|
||||||
|
#include "common/endian.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
|
||||||
|
|
||||||
#define ID_RIFF 0x46464952
|
#define ID_RIFF 0x46464952
|
||||||
|
@ -108,21 +108,13 @@ SampleFormat PcmDecoder::setHeader(msg::CodecHeader* chunk)
|
||||||
/// Unknown chunk, skip bytes
|
/// Unknown chunk, skip bytes
|
||||||
pos += SWAP_32(chunk_header.sz);
|
pos += SWAP_32(chunk_header.sz);
|
||||||
}
|
}
|
||||||
}
|
} while (moreChunks);
|
||||||
while (moreChunks);
|
|
||||||
|
|
||||||
|
|
||||||
if (SWAP_32(chunk_fmt.sample_rate) == 0)
|
if (SWAP_32(chunk_fmt.sample_rate) == 0)
|
||||||
throw SnapException("Sample format not found");
|
throw SnapException("Sample format not found");
|
||||||
|
|
||||||
SampleFormat sampleFormat(
|
SampleFormat sampleFormat(SWAP_32(chunk_fmt.sample_rate), SWAP_16(chunk_fmt.bits_per_sample), SWAP_16(chunk_fmt.num_channels));
|
||||||
SWAP_32(chunk_fmt.sample_rate),
|
|
||||||
SWAP_16(chunk_fmt.bits_per_sample),
|
|
||||||
SWAP_16(chunk_fmt.num_channels));
|
|
||||||
|
|
||||||
return sampleFormat;
|
return sampleFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -31,5 +31,3 @@ public:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,8 +19,8 @@
|
||||||
#ifndef DOUBLE_BUFFER_H
|
#ifndef DOUBLE_BUFFER_H
|
||||||
#define DOUBLE_BUFFER_H
|
#define DOUBLE_BUFFER_H
|
||||||
|
|
||||||
#include<deque>
|
#include <algorithm>
|
||||||
#include<algorithm>
|
#include <deque>
|
||||||
|
|
||||||
|
|
||||||
/// Size limited queue
|
/// Size limited queue
|
||||||
|
@ -63,10 +63,10 @@ public:
|
||||||
{
|
{
|
||||||
unsigned int low = tmpBuffer.size() / 2;
|
unsigned int low = tmpBuffer.size() / 2;
|
||||||
unsigned int high = low;
|
unsigned int high = low;
|
||||||
low -= mean/2;
|
low -= mean / 2;
|
||||||
high += mean/2;
|
high += mean / 2;
|
||||||
T result((T)0);
|
T result((T)0);
|
||||||
for (unsigned int i=low; i<=high; ++i)
|
for (unsigned int i = low; i <= high; ++i)
|
||||||
{
|
{
|
||||||
result += tmpBuffer[i];
|
result += tmpBuffer[i];
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ public:
|
||||||
if (buffer.empty())
|
if (buffer.empty())
|
||||||
return 0;
|
return 0;
|
||||||
double mean = 0.;
|
double mean = 0.;
|
||||||
for (size_t n=0; n<buffer.size(); ++n)
|
for (size_t n = 0; n < buffer.size(); ++n)
|
||||||
mean += (float)buffer[n] / (float)buffer.size();
|
mean += (float)buffer[n] / (float)buffer.size();
|
||||||
return mean;
|
return mean;
|
||||||
}
|
}
|
||||||
|
@ -126,11 +126,8 @@ public:
|
||||||
private:
|
private:
|
||||||
size_t bufferSize;
|
size_t bufferSize;
|
||||||
std::deque<T> buffer;
|
std::deque<T> buffer;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -88,7 +88,7 @@ protected:
|
||||||
/*
|
/*
|
||||||
* Send metadata to stderr as json
|
* Send metadata to stderr as json
|
||||||
*/
|
*/
|
||||||
class MetaStderrAdapter: public MetadataAdapter
|
class MetaStderrAdapter : public MetadataAdapter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using MetadataAdapter::push;
|
using MetadataAdapter::push;
|
||||||
|
@ -98,7 +98,6 @@ public:
|
||||||
std::cerr << serialize() << "\n";
|
std::cerr << serialize() << "\n";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -26,8 +26,7 @@
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
AlsaPlayer::AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) :
|
AlsaPlayer::AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : Player(pcmDevice, stream), handle_(NULL), buff_(NULL)
|
||||||
Player(pcmDevice, stream), handle_(NULL), buff_(NULL)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@ void AlsaPlayer::initAlsa()
|
||||||
{
|
{
|
||||||
unsigned int tmp, rate;
|
unsigned int tmp, rate;
|
||||||
int pcm, channels;
|
int pcm, channels;
|
||||||
snd_pcm_hw_params_t *params;
|
snd_pcm_hw_params_t* params;
|
||||||
int buff_size;
|
int buff_size;
|
||||||
|
|
||||||
const SampleFormat& format = stream_->getFormat();
|
const SampleFormat& format = stream_->getFormat();
|
||||||
|
@ -119,9 +118,9 @@ void AlsaPlayer::initAlsa()
|
||||||
snd_pcm_hw_params_set_period_time_near(handle_, params, &period_time, 0);
|
snd_pcm_hw_params_set_period_time_near(handle_, params, &period_time, 0);
|
||||||
snd_pcm_hw_params_set_buffer_time_near(handle_, params, &buffer_time, 0);
|
snd_pcm_hw_params_set_buffer_time_near(handle_, params, &buffer_time, 0);
|
||||||
|
|
||||||
// long unsigned int periodsize = stream_->format.msRate() * 50;//2*rate/50;
|
// long unsigned int periodsize = stream_->format.msRate() * 50;//2*rate/50;
|
||||||
// if ((pcm = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &periodsize)) < 0)
|
// if ((pcm = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &periodsize)) < 0)
|
||||||
// LOG(ERROR) << "Unable to set buffer size " << (long int)periodsize << ": " << snd_strerror(pcm) << "\n";
|
// LOG(ERROR) << "Unable to set buffer size " << (long int)periodsize << ": " << snd_strerror(pcm) << "\n";
|
||||||
|
|
||||||
/* Write parameters */
|
/* Write parameters */
|
||||||
if ((pcm = snd_pcm_hw_params(handle_, params)) < 0)
|
if ((pcm = snd_pcm_hw_params(handle_, params)) < 0)
|
||||||
|
@ -140,19 +139,19 @@ void AlsaPlayer::initAlsa()
|
||||||
snd_pcm_hw_params_get_period_size(params, &frames_, 0);
|
snd_pcm_hw_params_get_period_size(params, &frames_, 0);
|
||||||
LOG(INFO) << "frames: " << frames_ << "\n";
|
LOG(INFO) << "frames: " << frames_ << "\n";
|
||||||
|
|
||||||
buff_size = frames_ * format.frameSize; //channels * 2 /* 2 -> sample size */;
|
buff_size = frames_ * format.frameSize; // channels * 2 /* 2 -> sample size */;
|
||||||
buff_ = (char *) malloc(buff_size);
|
buff_ = (char*)malloc(buff_size);
|
||||||
|
|
||||||
snd_pcm_hw_params_get_period_time(params, &tmp, NULL);
|
snd_pcm_hw_params_get_period_time(params, &tmp, NULL);
|
||||||
LOG(DEBUG) << "period time: " << tmp << "\n";
|
LOG(DEBUG) << "period time: " << tmp << "\n";
|
||||||
|
|
||||||
snd_pcm_sw_params_t *swparams;
|
snd_pcm_sw_params_t* swparams;
|
||||||
snd_pcm_sw_params_alloca(&swparams);
|
snd_pcm_sw_params_alloca(&swparams);
|
||||||
snd_pcm_sw_params_current(handle_, swparams);
|
snd_pcm_sw_params_current(handle_, swparams);
|
||||||
|
|
||||||
snd_pcm_sw_params_set_avail_min(handle_, swparams, frames_);
|
snd_pcm_sw_params_set_avail_min(handle_, swparams, frames_);
|
||||||
snd_pcm_sw_params_set_start_threshold(handle_, swparams, frames_);
|
snd_pcm_sw_params_set_start_threshold(handle_, swparams, frames_);
|
||||||
// snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_);
|
// snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_);
|
||||||
snd_pcm_sw_params(handle_, swparams);
|
snd_pcm_sw_params(handle_, swparams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,10 +214,10 @@ void AlsaPlayer::worker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// snd_pcm_avail_delay(handle_, &framesAvail, &framesDelay);
|
// snd_pcm_avail_delay(handle_, &framesAvail, &framesDelay);
|
||||||
snd_pcm_delay(handle_, &framesDelay);
|
snd_pcm_delay(handle_, &framesDelay);
|
||||||
chronos::usec delay((chronos::usec::rep) (1000 * (double) framesDelay / stream_->getFormat().msRate()));
|
chronos::usec delay((chronos::usec::rep)(1000 * (double)framesDelay / stream_->getFormat().msRate()));
|
||||||
// LOG(INFO) << "delay: " << framesDelay << ", delay[ms]: " << delay.count() / 1000 << "\n";
|
// LOG(INFO) << "delay: " << framesDelay << ", delay[ms]: " << delay.count() / 1000 << "\n";
|
||||||
|
|
||||||
if (stream_->getPlayerChunk(buff_, delay, frames_))
|
if (stream_->getPlayerChunk(buff_, delay, frames_))
|
||||||
{
|
{
|
||||||
|
@ -273,15 +272,18 @@ vector<PcmDevice> AlsaPlayer::pcm_list(void)
|
||||||
if (io != NULL && strcmp(io, "Output") != 0)
|
if (io != NULL && strcmp(io, "Output") != 0)
|
||||||
goto __end;
|
goto __end;
|
||||||
pcmDevice.name = name;
|
pcmDevice.name = name;
|
||||||
if(descr == NULL) {
|
if (descr == NULL)
|
||||||
|
{
|
||||||
pcmDevice.description = "";
|
pcmDevice.description = "";
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
pcmDevice.description = descr;
|
pcmDevice.description = descr;
|
||||||
}
|
}
|
||||||
pcmDevice.idx = idx++;
|
pcmDevice.idx = idx++;
|
||||||
result.push_back(pcmDevice);
|
result.push_back(pcmDevice);
|
||||||
|
|
||||||
__end:
|
__end:
|
||||||
if (name != NULL)
|
if (name != NULL)
|
||||||
free(name);
|
free(name);
|
||||||
if (descr != NULL)
|
if (descr != NULL)
|
||||||
|
@ -293,4 +295,3 @@ __end:
|
||||||
snd_device_name_free_hint(hints);
|
snd_device_name_free_hint(hints);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -49,9 +49,8 @@ private:
|
||||||
|
|
||||||
snd_pcm_t* handle_;
|
snd_pcm_t* handle_;
|
||||||
snd_pcm_uframes_t frames_;
|
snd_pcm_uframes_t frames_;
|
||||||
char *buff_;
|
char* buff_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,26 +17,23 @@
|
||||||
***/
|
***/
|
||||||
|
|
||||||
//#include <CoreServices/CoreServices.h>
|
//#include <CoreServices/CoreServices.h>
|
||||||
#include <CoreAudio/CoreAudio.h>
|
|
||||||
#include "coreAudioPlayer.h"
|
#include "coreAudioPlayer.h"
|
||||||
|
#include <CoreAudio/CoreAudio.h>
|
||||||
|
|
||||||
#define NUM_BUFFERS 2
|
#define NUM_BUFFERS 2
|
||||||
|
|
||||||
|
|
||||||
//http://stackoverflow.com/questions/4863811/how-to-use-audioqueue-to-play-a-sound-for-mac-osx-in-c
|
// http://stackoverflow.com/questions/4863811/how-to-use-audioqueue-to-play-a-sound-for-mac-osx-in-c
|
||||||
//https://gist.github.com/andormade/1360885
|
// https://gist.github.com/andormade/1360885
|
||||||
|
|
||||||
void callback(void *custom_data, AudioQueueRef queue, AudioQueueBufferRef buffer)
|
void callback(void* custom_data, AudioQueueRef queue, AudioQueueBufferRef buffer)
|
||||||
{
|
{
|
||||||
CoreAudioPlayer* player = static_cast<CoreAudioPlayer*>(custom_data);
|
CoreAudioPlayer* player = static_cast<CoreAudioPlayer*>(custom_data);
|
||||||
player->playerCallback(queue, buffer);
|
player->playerCallback(queue, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CoreAudioPlayer::CoreAudioPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) :
|
CoreAudioPlayer::CoreAudioPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : Player(pcmDevice, stream), ms_(100), pubStream_(stream)
|
||||||
Player(pcmDevice, stream),
|
|
||||||
ms_(100),
|
|
||||||
pubStream_(stream)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,13 +48,11 @@ std::vector<PcmDevice> CoreAudioPlayer::pcm_list(void)
|
||||||
{
|
{
|
||||||
UInt32 propsize;
|
UInt32 propsize;
|
||||||
|
|
||||||
AudioObjectPropertyAddress theAddress = { kAudioHardwarePropertyDevices,
|
AudioObjectPropertyAddress theAddress = {kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
|
||||||
kAudioObjectPropertyScopeGlobal,
|
|
||||||
kAudioObjectPropertyElementMaster };
|
|
||||||
|
|
||||||
AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize);
|
AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize);
|
||||||
int nDevices = propsize / sizeof(AudioDeviceID);
|
int nDevices = propsize / sizeof(AudioDeviceID);
|
||||||
AudioDeviceID *devids = new AudioDeviceID[nDevices];
|
AudioDeviceID* devids = new AudioDeviceID[nDevices];
|
||||||
AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids);
|
AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids);
|
||||||
|
|
||||||
std::vector<PcmDevice> result;
|
std::vector<PcmDevice> result;
|
||||||
|
@ -67,11 +62,11 @@ std::vector<PcmDevice> CoreAudioPlayer::pcm_list(void)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
UInt32 propSize;
|
UInt32 propSize;
|
||||||
AudioObjectPropertyAddress theAddress = { kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyScopeOutput, 0 };
|
AudioObjectPropertyAddress theAddress = {kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyScopeOutput, 0};
|
||||||
if (AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propSize))
|
if (AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propSize))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
AudioBufferList *buflist = (AudioBufferList *)malloc(propSize);
|
AudioBufferList* buflist = (AudioBufferList*)malloc(propSize);
|
||||||
if (AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propSize, buflist))
|
if (AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propSize, buflist))
|
||||||
continue;
|
continue;
|
||||||
int channels = 0;
|
int channels = 0;
|
||||||
|
@ -83,7 +78,7 @@ std::vector<PcmDevice> CoreAudioPlayer::pcm_list(void)
|
||||||
|
|
||||||
UInt32 maxlen = 1024;
|
UInt32 maxlen = 1024;
|
||||||
char buf[maxlen];
|
char buf[maxlen];
|
||||||
theAddress = { kAudioDevicePropertyDeviceName, kAudioDevicePropertyScopeOutput, 0 };
|
theAddress = {kAudioDevicePropertyDeviceName, kAudioDevicePropertyScopeOutput, 0};
|
||||||
AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &maxlen, buf);
|
AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &maxlen, buf);
|
||||||
LOG(DEBUG) << "device: " << i << ", name: " << buf << ", channels: " << channels << "\n";
|
LOG(DEBUG) << "device: " << i << ", name: " << buf << ", channels: " << channels << "\n";
|
||||||
|
|
||||||
|
@ -104,11 +99,11 @@ void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bu
|
||||||
size_t bufferedMs = bufferedFrames * 1000 / pubStream_->getFormat().rate + (ms_ * (NUM_BUFFERS - 1));
|
size_t bufferedMs = bufferedFrames * 1000 / pubStream_->getFormat().rate + (ms_ * (NUM_BUFFERS - 1));
|
||||||
/// 15ms DAC delay. Based on trying.
|
/// 15ms DAC delay. Based on trying.
|
||||||
bufferedMs += 15;
|
bufferedMs += 15;
|
||||||
// LOG(INFO) << "buffered: " << bufferedFrames << ", ms: " << bufferedMs << ", mSampleTime: " << timestamp.mSampleTime << "\n";
|
// LOG(INFO) << "buffered: " << bufferedFrames << ", ms: " << bufferedMs << ", mSampleTime: " << timestamp.mSampleTime << "\n";
|
||||||
|
|
||||||
/// TODO: sometimes this bufferedMS or AudioTimeStamp wraps around 1s (i.e. we're 1s out of sync (behind)) and recovers later on
|
/// TODO: sometimes this bufferedMS or AudioTimeStamp wraps around 1s (i.e. we're 1s out of sync (behind)) and recovers later on
|
||||||
chronos::usec delay(bufferedMs * 1000);
|
chronos::usec delay(bufferedMs * 1000);
|
||||||
char *buffer = (char*)bufferRef->mAudioData;
|
char* buffer = (char*)bufferRef->mAudioData;
|
||||||
if (!pubStream_->getPlayerChunk(buffer, delay, frames_))
|
if (!pubStream_->getPlayerChunk(buffer, delay, frames_))
|
||||||
{
|
{
|
||||||
if (chronos::getTickCount() - lastChunkTick > 5000)
|
if (chronos::getTickCount() - lastChunkTick > 5000)
|
||||||
|
@ -117,7 +112,7 @@ void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bu
|
||||||
uninitAudioQueue(queue);
|
uninitAudioQueue(queue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// LOG(INFO) << "Failed to get chunk. Playing silence.\n";
|
// LOG(INFO) << "Failed to get chunk. Playing silence.\n";
|
||||||
memset(buffer, 0, buff_size_);
|
memset(buffer, 0, buff_size_);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -126,7 +121,7 @@ void CoreAudioPlayer::playerCallback(AudioQueueRef queue, AudioQueueBufferRef bu
|
||||||
adjustVolume(buffer, frames_);
|
adjustVolume(buffer, frames_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// OSStatus status =
|
// OSStatus status =
|
||||||
AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL);
|
AudioQueueEnqueueBuffer(queue, bufferRef, 0, NULL);
|
||||||
|
|
||||||
if (!active_)
|
if (!active_)
|
||||||
|
@ -163,7 +158,7 @@ void CoreAudioPlayer::initAudioQueue()
|
||||||
AudioStreamBasicDescription format;
|
AudioStreamBasicDescription format;
|
||||||
format.mSampleRate = sampleFormat.rate;
|
format.mSampleRate = sampleFormat.rate;
|
||||||
format.mFormatID = kAudioFormatLinearPCM;
|
format.mFormatID = kAudioFormatLinearPCM;
|
||||||
format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;// | kAudioFormatFlagIsPacked;
|
format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; // | kAudioFormatFlagIsPacked;
|
||||||
format.mBitsPerChannel = sampleFormat.bits;
|
format.mBitsPerChannel = sampleFormat.bits;
|
||||||
format.mChannelsPerFrame = sampleFormat.channels;
|
format.mChannelsPerFrame = sampleFormat.channels;
|
||||||
format.mBytesPerFrame = sampleFormat.frameSize;
|
format.mBytesPerFrame = sampleFormat.frameSize;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -58,4 +58,3 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "openslPlayer.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
#include "common/strCompat.h"
|
#include "common/strCompat.h"
|
||||||
|
#include "openslPlayer.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ using namespace std;
|
||||||
// The documentation available is very unclear about how to best manage buffers.
|
// The documentation available is very unclear about how to best manage buffers.
|
||||||
// I've chosen to this approach: Instantly enqueue a buffer that was rendered to the last time,
|
// I've chosen to this approach: Instantly enqueue a buffer that was rendered to the last time,
|
||||||
// and then render the next. Hopefully it's okay to spend time in this callback after having enqueued.
|
// and then render the next. Hopefully it's okay to spend time in this callback after having enqueued.
|
||||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
||||||
{
|
{
|
||||||
OpenslPlayer* player = static_cast<OpenslPlayer*>(context);
|
OpenslPlayer* player = static_cast<OpenslPlayer*>(context);
|
||||||
player->playerCallback(bq);
|
player->playerCallback(bq);
|
||||||
|
@ -43,19 +43,9 @@ static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
OpenslPlayer::OpenslPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) :
|
OpenslPlayer::OpenslPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream)
|
||||||
Player(pcmDevice, stream),
|
: Player(pcmDevice, stream), engineObject(NULL), engineEngine(NULL), outputMixObject(NULL), bqPlayerObject(NULL), bqPlayerPlay(NULL),
|
||||||
engineObject(NULL),
|
bqPlayerBufferQueue(NULL), bqPlayerVolume(NULL), curBuffer(0), ms_(50), buff_size(0), pubStream_(stream)
|
||||||
engineEngine(NULL),
|
|
||||||
outputMixObject(NULL),
|
|
||||||
bqPlayerObject(NULL),
|
|
||||||
bqPlayerPlay(NULL),
|
|
||||||
bqPlayerBufferQueue(NULL),
|
|
||||||
bqPlayerVolume(NULL),
|
|
||||||
curBuffer(0),
|
|
||||||
ms_(50),
|
|
||||||
buff_size(0),
|
|
||||||
pubStream_(stream)
|
|
||||||
{
|
{
|
||||||
initOpensl();
|
initOpensl();
|
||||||
}
|
}
|
||||||
|
@ -75,33 +65,33 @@ void OpenslPlayer::playerCallback(SLAndroidSimpleBufferQueueItf bq)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static long lastTick = 0;
|
/* static long lastTick = 0;
|
||||||
long now = chronos::getTickCount();
|
long now = chronos::getTickCount();
|
||||||
int diff = 0;
|
int diff = 0;
|
||||||
if (lastTick != 0)
|
if (lastTick != 0)
|
||||||
{
|
{
|
||||||
diff = now - lastTick;
|
diff = now - lastTick;
|
||||||
// LOG(ERROR) << "diff: " << diff << ", frames: " << player->frames_ / 44.1 << "\n";
|
// LOG(ERROR) << "diff: " << diff << ", frames: " << player->frames_ / 44.1 << "\n";
|
||||||
// if (diff <= 50)
|
// if (diff <= 50)
|
||||||
// {
|
// {
|
||||||
// usleep(1000 * (50 - diff));
|
// usleep(1000 * (50 - diff));
|
||||||
// diff = 50;
|
// diff = 50;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
lastTick = chronos::getTickCount();
|
lastTick = chronos::getTickCount();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// size_t d = player->frames_ / 0.48d;
|
// size_t d = player->frames_ / 0.48d;
|
||||||
// LOG(ERROR) << "Delay: " << d << "\n";
|
// LOG(ERROR) << "Delay: " << d << "\n";
|
||||||
// SLAndroidSimpleBufferQueueState state;
|
// SLAndroidSimpleBufferQueueState state;
|
||||||
// (*bq)->GetState(bq, &state);
|
// (*bq)->GetState(bq, &state);
|
||||||
// cout << "bqPlayerCallback count: " << state.count << ", idx: " << state.index << "\n";
|
// cout << "bqPlayerCallback count: " << state.count << ", idx: " << state.index << "\n";
|
||||||
|
|
||||||
// diff = 0;
|
// diff = 0;
|
||||||
// chronos::usec delay((250 - diff) * 1000);
|
// chronos::usec delay((250 - diff) * 1000);
|
||||||
|
|
||||||
// while (active_ && !stream_->waitForChunk(100))
|
// while (active_ && !stream_->waitForChunk(100))
|
||||||
// LOG(INFO) << "Waiting for chunk\n";
|
// LOG(INFO) << "Waiting for chunk\n";
|
||||||
|
|
||||||
if (!active_)
|
if (!active_)
|
||||||
return;
|
return;
|
||||||
|
@ -109,7 +99,7 @@ void OpenslPlayer::playerCallback(SLAndroidSimpleBufferQueueItf bq)
|
||||||
chronos::usec delay(ms_ * 1000);
|
chronos::usec delay(ms_ * 1000);
|
||||||
if (!pubStream_->getPlayerChunk(buffer[curBuffer], delay, frames_))
|
if (!pubStream_->getPlayerChunk(buffer[curBuffer], delay, frames_))
|
||||||
{
|
{
|
||||||
// LOG(INFO) << "Failed to get chunk. Playing silence.\n";
|
// LOG(INFO) << "Failed to get chunk. Playing silence.\n";
|
||||||
memset(buffer[curBuffer], 0, buff_size);
|
memset(buffer[curBuffer], 0, buff_size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -132,7 +122,7 @@ void OpenslPlayer::playerCallback(SLAndroidSimpleBufferQueueItf bq)
|
||||||
|
|
||||||
std::string OpenslPlayer::resultToString(SLresult result) const
|
std::string OpenslPlayer::resultToString(SLresult result) const
|
||||||
{
|
{
|
||||||
switch(result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case SL_RESULT_SUCCESS:
|
case SL_RESULT_SUCCESS:
|
||||||
return "SL_RESULT_SUCCESS";
|
return "SL_RESULT_SUCCESS";
|
||||||
|
@ -193,17 +183,14 @@ void OpenslPlayer::initOpensl()
|
||||||
const SampleFormat& format = stream_->getFormat();
|
const SampleFormat& format = stream_->getFormat();
|
||||||
|
|
||||||
|
|
||||||
frames_ = format.rate / (1000 / ms_);// * format.channels; // 1920; // 48000 * 2 / 50 // => 50ms
|
frames_ = format.rate / (1000 / ms_); // * format.channels; // 1920; // 48000 * 2 / 50 // => 50ms
|
||||||
|
|
||||||
buff_size = frames_ * format.frameSize /* 2 -> sample size */;
|
buff_size = frames_ * format.frameSize /* 2 -> sample size */;
|
||||||
LOG(INFO) << "frames: " << frames_ << ", channels: " << format.channels << ", rate: " << format.rate << ", buff: " << buff_size << "\n";
|
LOG(INFO) << "frames: " << frames_ << ", channels: " << format.channels << ", rate: " << format.rate << ", buff: " << buff_size << "\n";
|
||||||
|
|
||||||
SLresult result;
|
SLresult result;
|
||||||
// create engine
|
// create engine
|
||||||
SLEngineOption engineOption[] =
|
SLEngineOption engineOption[] = {{(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}};
|
||||||
{
|
|
||||||
{(SLuint32) SL_ENGINEOPTION_THREADSAFE, (SLuint32) SL_BOOLEAN_TRUE}
|
|
||||||
};
|
|
||||||
result = slCreateEngine(&engineObject, 1, engineOption, 0, NULL, NULL);
|
result = slCreateEngine(&engineObject, 1, engineOption, 0, NULL, NULL);
|
||||||
throwUnsuccess("slCreateEngine", result);
|
throwUnsuccess("slCreateEngine", result);
|
||||||
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
||||||
|
@ -216,7 +203,7 @@ void OpenslPlayer::initOpensl()
|
||||||
throwUnsuccess("OutputMixObject::Realize", result);
|
throwUnsuccess("OutputMixObject::Realize", result);
|
||||||
|
|
||||||
SLuint32 samplesPerSec = SL_SAMPLINGRATE_48;
|
SLuint32 samplesPerSec = SL_SAMPLINGRATE_48;
|
||||||
switch(format.rate)
|
switch (format.rate)
|
||||||
{
|
{
|
||||||
case 8000:
|
case 8000:
|
||||||
samplesPerSec = SL_SAMPLINGRATE_8;
|
samplesPerSec = SL_SAMPLINGRATE_8;
|
||||||
|
@ -260,7 +247,7 @@ void OpenslPlayer::initOpensl()
|
||||||
|
|
||||||
SLuint32 bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
|
SLuint32 bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
|
||||||
SLuint32 containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
|
SLuint32 containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
|
||||||
switch(format.bits)
|
switch (format.bits)
|
||||||
{
|
{
|
||||||
case 8:
|
case 8:
|
||||||
bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8;
|
bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8;
|
||||||
|
@ -284,16 +271,9 @@ void OpenslPlayer::initOpensl()
|
||||||
|
|
||||||
|
|
||||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
||||||
SLDataFormat_PCM format_pcm =
|
SLDataFormat_PCM format_pcm = {
|
||||||
{
|
SL_DATAFORMAT_PCM, format.channels, samplesPerSec, bitsPerSample, containerSize, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
||||||
SL_DATAFORMAT_PCM,
|
SL_BYTEORDER_LITTLEENDIAN};
|
||||||
format.channels,
|
|
||||||
samplesPerSec,
|
|
||||||
bitsPerSample,
|
|
||||||
containerSize,
|
|
||||||
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
|
||||||
SL_BYTEORDER_LITTLEENDIAN
|
|
||||||
};
|
|
||||||
|
|
||||||
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
||||||
|
|
||||||
|
@ -302,8 +282,8 @@ void OpenslPlayer::initOpensl()
|
||||||
SLDataSink audioSnk = {&loc_outmix, NULL};
|
SLDataSink audioSnk = {&loc_outmix, NULL};
|
||||||
|
|
||||||
// create audio player
|
// create audio player
|
||||||
const SLInterfaceID ids[3] = {SL_IID_ANDROIDCONFIGURATION, SL_IID_PLAY, SL_IID_BUFFERQUEUE};//, SL_IID_VOLUME};
|
const SLInterfaceID ids[3] = {SL_IID_ANDROIDCONFIGURATION, SL_IID_PLAY, SL_IID_BUFFERQUEUE}; //, SL_IID_VOLUME};
|
||||||
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};//, SL_BOOLEAN_TRUE};
|
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; //, SL_BOOLEAN_TRUE};
|
||||||
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 3, ids, req);
|
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 3, ids, req);
|
||||||
throwUnsuccess("Engine::CreateAudioPlayer", result);
|
throwUnsuccess("Engine::CreateAudioPlayer", result);
|
||||||
|
|
||||||
|
@ -311,7 +291,7 @@ void OpenslPlayer::initOpensl()
|
||||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDCONFIGURATION, &playerConfig);
|
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDCONFIGURATION, &playerConfig);
|
||||||
throwUnsuccess("PlayerObject::GetInterface", result);
|
throwUnsuccess("PlayerObject::GetInterface", result);
|
||||||
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
|
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
|
||||||
//// SLint32 streamType = SL_ANDROID_STREAM_VOICE;
|
//// SLint32 streamType = SL_ANDROID_STREAM_VOICE;
|
||||||
result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32));
|
result = (*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32));
|
||||||
throwUnsuccess("PlayerConfig::SetConfiguration", result);
|
throwUnsuccess("PlayerConfig::SetConfiguration", result);
|
||||||
|
|
||||||
|
@ -323,8 +303,8 @@ void OpenslPlayer::initOpensl()
|
||||||
throwUnsuccess("PlayerObject::GetInterface", result);
|
throwUnsuccess("PlayerObject::GetInterface", result);
|
||||||
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
|
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
|
||||||
throwUnsuccess("PlayerBufferQueue::RegisterCallback", result);
|
throwUnsuccess("PlayerBufferQueue::RegisterCallback", result);
|
||||||
// result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
|
// result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
|
||||||
// throwUnsuccess("PlayerObject::GetInterface", result);
|
// throwUnsuccess("PlayerObject::GetInterface", result);
|
||||||
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);
|
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);
|
||||||
throwUnsuccess("PlayerPlay::SetPlayState", result);
|
throwUnsuccess("PlayerPlay::SetPlayState", result);
|
||||||
|
|
||||||
|
@ -344,8 +324,8 @@ void OpenslPlayer::initOpensl()
|
||||||
|
|
||||||
void OpenslPlayer::uninitOpensl()
|
void OpenslPlayer::uninitOpensl()
|
||||||
{
|
{
|
||||||
// if (!active_)
|
// if (!active_)
|
||||||
// return;
|
// return;
|
||||||
|
|
||||||
LOG(INFO) << "uninitOpensl\n";
|
LOG(INFO) << "uninitOpensl\n";
|
||||||
SLresult result;
|
SLresult result;
|
||||||
|
@ -385,10 +365,10 @@ void OpenslPlayer::uninitOpensl()
|
||||||
engineEngine = NULL;
|
engineEngine = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete [] buffer[0];
|
delete[] buffer[0];
|
||||||
buffer[0] = NULL;
|
buffer[0] = NULL;
|
||||||
|
|
||||||
delete [] buffer[1];
|
delete[] buffer[1];
|
||||||
buffer[1] = NULL;
|
buffer[1] = NULL;
|
||||||
|
|
||||||
LOG(INFO) << "OpenSLWrap_Shutdown - finished\n";
|
LOG(INFO) << "OpenSLWrap_Shutdown - finished\n";
|
||||||
|
@ -413,4 +393,3 @@ void OpenslPlayer::stop()
|
||||||
void OpenslPlayer::worker()
|
void OpenslPlayer::worker()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
|
|
||||||
typedef int (*AndroidAudioCallback)(short *buffer, int num_samples);
|
typedef int (*AndroidAudioCallback)(short* buffer, int num_samples);
|
||||||
|
|
||||||
|
|
||||||
/// OpenSL Audio Player
|
/// OpenSL Audio Player
|
||||||
|
@ -64,7 +64,7 @@ protected:
|
||||||
|
|
||||||
// Double buffering.
|
// Double buffering.
|
||||||
int curBuffer;
|
int curBuffer;
|
||||||
char *buffer[2];
|
char* buffer[2];
|
||||||
|
|
||||||
size_t ms_;
|
size_t ms_;
|
||||||
size_t frames_;
|
size_t frames_;
|
||||||
|
@ -74,4 +74,3 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,15 +24,9 @@
|
||||||
|
|
||||||
struct PcmDevice
|
struct PcmDevice
|
||||||
{
|
{
|
||||||
PcmDevice() :
|
PcmDevice() : idx(-1), name("default"){};
|
||||||
idx(-1), name("default")
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
PcmDevice(int idx, const std::string& name, const std::string& description = "") :
|
PcmDevice(int idx, const std::string& name, const std::string& description = "") : idx(idx), name(name), description(description){};
|
||||||
idx(idx), name(name), description(description)
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
int idx;
|
int idx;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
@ -41,4 +35,3 @@ struct PcmDevice
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,23 +16,18 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "player.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "player.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
Player::Player(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) :
|
Player::Player(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream)
|
||||||
active_(false),
|
: active_(false), stream_(stream), pcmDevice_(pcmDevice), volume_(1.0), muted_(false), volCorrection_(1.0)
|
||||||
stream_(stream),
|
|
||||||
pcmDevice_(pcmDevice),
|
|
||||||
volume_(1.0),
|
|
||||||
muted_(false),
|
|
||||||
volCorrection_(1.0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,18 +69,18 @@ void Player::adjustVolume(char* buffer, size_t frames)
|
||||||
{
|
{
|
||||||
volume *= volCorrection_;
|
volume *= volCorrection_;
|
||||||
if (sampleFormat.sampleSize == 1)
|
if (sampleFormat.sampleSize == 1)
|
||||||
adjustVolume<int8_t>(buffer, frames*sampleFormat.channels, volume);
|
adjustVolume<int8_t>(buffer, frames * sampleFormat.channels, volume);
|
||||||
else if (sampleFormat.sampleSize == 2)
|
else if (sampleFormat.sampleSize == 2)
|
||||||
adjustVolume<int16_t>(buffer, frames*sampleFormat.channels, volume);
|
adjustVolume<int16_t>(buffer, frames * sampleFormat.channels, volume);
|
||||||
else if (sampleFormat.sampleSize == 4)
|
else if (sampleFormat.sampleSize == 4)
|
||||||
adjustVolume<int32_t>(buffer, frames*sampleFormat.channels, volume);
|
adjustVolume<int32_t>(buffer, frames * sampleFormat.channels, volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/src/pulse/volume.c#n260
|
// https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/src/pulse/volume.c#n260
|
||||||
//http://www.robotplanet.dk/audio/audio_gui_design/
|
// http://www.robotplanet.dk/audio/audio_gui_design/
|
||||||
//https://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#22198
|
// https://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#22198
|
||||||
void Player::setVolume_poly(double volume, double exp)
|
void Player::setVolume_poly(double volume, double exp)
|
||||||
{
|
{
|
||||||
volume_ = std::pow(volume, exp);
|
volume_ = std::pow(volume, exp);
|
||||||
|
@ -93,12 +88,12 @@ void Player::setVolume_poly(double volume, double exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//http://stackoverflow.com/questions/1165026/what-algorithms-could-i-use-for-audio-volume-level
|
// http://stackoverflow.com/questions/1165026/what-algorithms-could-i-use-for-audio-volume-level
|
||||||
void Player::setVolume_exp(double volume, double base)
|
void Player::setVolume_exp(double volume, double base)
|
||||||
{
|
{
|
||||||
// double base = M_E;
|
// double base = M_E;
|
||||||
// double base = 10.;
|
// double base = 10.;
|
||||||
volume_ = (pow(base, volume)-1) / (base-1);
|
volume_ = (pow(base, volume) - 1) / (base - 1);
|
||||||
LOG(DEBUG) << "setVolume exp: " << volume << " => " << volume_ << "\n";
|
LOG(DEBUG) << "setVolume exp: " << volume << " => " << volume_ << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,5 +108,3 @@ void Player::setMute(bool mute)
|
||||||
{
|
{
|
||||||
muted_ = mute;
|
muted_ = mute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,14 +19,14 @@
|
||||||
#ifndef PLAYER_H
|
#ifndef PLAYER_H
|
||||||
#define PLAYER_H
|
#define PLAYER_H
|
||||||
|
|
||||||
|
#include "aixlog.hpp"
|
||||||
|
#include "common/endian.hpp"
|
||||||
|
#include "pcmDevice.h"
|
||||||
|
#include "stream.h"
|
||||||
|
#include <atomic>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "stream.h"
|
|
||||||
#include "pcmDevice.h"
|
|
||||||
#include "common/endian.hpp"
|
|
||||||
#include "aixlog.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
/// Audio Player
|
/// Audio Player
|
||||||
|
@ -52,10 +52,10 @@ protected:
|
||||||
void setVolume_exp(double volume, double base);
|
void setVolume_exp(double volume, double base);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void adjustVolume(char *buffer, size_t count, double volume)
|
void adjustVolume(char* buffer, size_t count, double volume)
|
||||||
{
|
{
|
||||||
T* bufferT = (T*)buffer;
|
T* bufferT = (T*)buffer;
|
||||||
for (size_t n=0; n<count; ++n)
|
for (size_t n = 0; n < count; ++n)
|
||||||
bufferT[n] = endian::swap<T>(endian::swap<T>(bufferT[n]) * volume);
|
bufferT[n] = endian::swap<T>(endian::swap<T>(bufferT[n]) * volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,4 +72,3 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
|
|
||||||
#include "popl.hpp"
|
|
||||||
#include "controller.h"
|
|
||||||
#include "browseZeroConf/browsemDNS.h"
|
#include "browseZeroConf/browsemDNS.h"
|
||||||
|
#include "controller.h"
|
||||||
|
#include "popl.hpp"
|
||||||
|
|
||||||
#ifdef HAS_ALSA
|
#ifdef HAS_ALSA
|
||||||
#include "player/alsaPlayer.h"
|
#include "player/alsaPlayer.h"
|
||||||
|
@ -49,15 +49,15 @@ PcmDevice getPcmDevice(const std::string& soundcard)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int soundcardIdx = cpt::stoi(soundcard);
|
int soundcardIdx = cpt::stoi(soundcard);
|
||||||
for (auto dev: pcmDevices)
|
for (auto dev : pcmDevices)
|
||||||
if (dev.idx == soundcardIdx)
|
if (dev.idx == soundcardIdx)
|
||||||
return dev;
|
return dev;
|
||||||
}
|
}
|
||||||
catch(...)
|
catch (...)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto dev: pcmDevices)
|
for (auto dev : pcmDevices)
|
||||||
if (dev.name.find(soundcard) != string::npos)
|
if (dev.name.find(soundcard) != string::npos)
|
||||||
return dev;
|
return dev;
|
||||||
#endif
|
#endif
|
||||||
|
@ -66,7 +66,7 @@ PcmDevice getPcmDevice(const std::string& soundcard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main (int argc, char **argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
#ifdef MACOS
|
#ifdef MACOS
|
||||||
#pragma message "Warning: the macOS support is experimental and might not be maintained"
|
#pragma message "Warning: the macOS support is experimental and might not be maintained"
|
||||||
|
@ -88,19 +88,19 @@ int main (int argc, char **argv)
|
||||||
auto versionSwitch = op.add<Switch>("v", "version", "show version number");
|
auto versionSwitch = op.add<Switch>("v", "version", "show version number");
|
||||||
#if defined(HAS_ALSA)
|
#if defined(HAS_ALSA)
|
||||||
auto listSwitch = op.add<Switch>("l", "list", "list pcm devices");
|
auto listSwitch = op.add<Switch>("l", "list", "list pcm devices");
|
||||||
/*auto soundcardValue =*/ op.add<Value<string>>("s", "soundcard", "index or name of the soundcard", "default", &soundcard);
|
/*auto soundcardValue =*/op.add<Value<string>>("s", "soundcard", "index or name of the soundcard", "default", &soundcard);
|
||||||
#endif
|
#endif
|
||||||
auto metaStderr = op.add<Switch>("e", "mstderr", "send metadata to stderr");
|
auto metaStderr = op.add<Switch>("e", "mstderr", "send metadata to stderr");
|
||||||
//auto metaHook = op.add<Value<string>>("m", "mhook", "script to call on meta tags", "", &meta_script);
|
// auto metaHook = op.add<Value<string>>("m", "mhook", "script to call on meta tags", "", &meta_script);
|
||||||
/*auto hostValue =*/ op.add<Value<string>>("h", "host", "server hostname or ip address", "", &host);
|
/*auto hostValue =*/op.add<Value<string>>("h", "host", "server hostname or ip address", "", &host);
|
||||||
/*auto portValue =*/ op.add<Value<size_t>>("p", "port", "server port", 1704, &port);
|
/*auto portValue =*/op.add<Value<size_t>>("p", "port", "server port", 1704, &port);
|
||||||
#ifdef HAS_DAEMON
|
#ifdef HAS_DAEMON
|
||||||
int processPriority(-3);
|
int processPriority(-3);
|
||||||
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "daemonize, optional process priority [-20..19]", -3, &processPriority);
|
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "daemonize, optional process priority [-20..19]", -3, &processPriority);
|
||||||
auto userValue = op.add<Value<string>>("", "user", "the user[:group] to run snapclient as when daemonized");
|
auto userValue = op.add<Value<string>>("", "user", "the user[:group] to run snapclient as when daemonized");
|
||||||
#endif
|
#endif
|
||||||
/*auto latencyValue =*/ op.add<Value<int>>("", "latency", "latency of the soundcard", 0, &latency);
|
/*auto latencyValue =*/op.add<Value<int>>("", "latency", "latency of the soundcard", 0, &latency);
|
||||||
/*auto instanceValue =*/ op.add<Value<size_t>>("i", "instance", "instance id", 1, &instance);
|
/*auto instanceValue =*/op.add<Value<size_t>>("i", "instance", "instance id", 1, &instance);
|
||||||
auto hostIdValue = op.add<Value<string>>("", "hostID", "unique host id", "");
|
auto hostIdValue = op.add<Value<string>>("", "hostID", "unique host id", "");
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -129,10 +129,9 @@ int main (int argc, char **argv)
|
||||||
if (listSwitch->is_set())
|
if (listSwitch->is_set())
|
||||||
{
|
{
|
||||||
vector<PcmDevice> pcmDevices = AlsaPlayer::pcm_list();
|
vector<PcmDevice> pcmDevices = AlsaPlayer::pcm_list();
|
||||||
for (auto dev: pcmDevices)
|
for (auto dev : pcmDevices)
|
||||||
{
|
{
|
||||||
cout << dev.idx << ": " << dev.name << "\n"
|
cout << dev.idx << ": " << dev.name << "\n" << dev.description << "\n\n";
|
||||||
<< dev.description << "\n\n";
|
|
||||||
}
|
}
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
@ -162,7 +161,8 @@ int main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
||||||
if (!debugOption->value().empty())
|
if (!debugOption->value().empty())
|
||||||
AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(), "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(),
|
||||||
|
"%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -211,7 +211,7 @@ int main (int argc, char **argv)
|
||||||
if (pcmDevice.idx == -1)
|
if (pcmDevice.idx == -1)
|
||||||
{
|
{
|
||||||
cout << "soundcard \"" << soundcard << "\" not found\n";
|
cout << "soundcard \"" << soundcard << "\" not found\n";
|
||||||
// exit(EXIT_FAILURE);
|
// exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ int main (int argc, char **argv)
|
||||||
// Setup metadata handling
|
// Setup metadata handling
|
||||||
std::shared_ptr<MetadataAdapter> meta;
|
std::shared_ptr<MetadataAdapter> meta;
|
||||||
meta.reset(new MetadataAdapter);
|
meta.reset(new MetadataAdapter);
|
||||||
if(metaStderr)
|
if (metaStderr)
|
||||||
meta.reset(new MetaStderrAdapter);
|
meta.reset(new MetaStderrAdapter);
|
||||||
|
|
||||||
std::unique_ptr<Controller> controller(new Controller(hostIdValue->value(), instance, meta));
|
std::unique_ptr<Controller> controller(new Controller(hostIdValue->value(), instance, meta));
|
||||||
|
@ -254,7 +254,7 @@ int main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
LOG(INFO) << "Latency: " << latency << "\n";
|
LOG(INFO) << "Latency: " << latency << "\n";
|
||||||
controller->start(pcmDevice, host, port, latency);
|
controller->start(pcmDevice, host, port, latency);
|
||||||
while(!g_terminated)
|
while (!g_terminated)
|
||||||
chronos::sleep(100);
|
chronos::sleep(100);
|
||||||
controller->stop();
|
controller->stop();
|
||||||
}
|
}
|
||||||
|
@ -268,5 +268,3 @@ int main (int argc, char **argv)
|
||||||
SLOG(NOTICE) << "daemon terminated." << endl;
|
SLOG(NOTICE) << "daemon terminated." << endl;
|
||||||
exit(exitcode);
|
exit(exitcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,31 +17,32 @@
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
|
#include "aixlog.hpp"
|
||||||
|
#include "timeProvider.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "aixlog.hpp"
|
|
||||||
#include "timeProvider.h"
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
//using namespace chronos;
|
// using namespace chronos;
|
||||||
namespace cs = chronos;
|
namespace cs = chronos;
|
||||||
|
|
||||||
|
|
||||||
Stream::Stream(const SampleFormat& sampleFormat) : format_(sampleFormat), sleep_(0), median_(0), shortMedian_(0), lastUpdate_(0), playedFrames_(0), bufferMs_(cs::msec(500))
|
Stream::Stream(const SampleFormat& sampleFormat)
|
||||||
|
: format_(sampleFormat), sleep_(0), median_(0), shortMedian_(0), lastUpdate_(0), playedFrames_(0), bufferMs_(cs::msec(500))
|
||||||
{
|
{
|
||||||
buffer_.setSize(500);
|
buffer_.setSize(500);
|
||||||
shortBuffer_.setSize(100);
|
shortBuffer_.setSize(100);
|
||||||
miniBuffer_.setSize(20);
|
miniBuffer_.setSize(20);
|
||||||
// cardBuffer_.setSize(50);
|
// cardBuffer_.setSize(50);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
48000 x
|
48000 x
|
||||||
------- = -----
|
------- = -----
|
||||||
47999,2 x - 1
|
47999,2 x - 1
|
||||||
|
|
||||||
x = 1,000016667 / (1,000016667 - 1)
|
x = 1,000016667 / (1,000016667 - 1)
|
||||||
*/
|
*/
|
||||||
setRealSampleRate(format_.rate);
|
setRealSampleRate(format_.rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ void Stream::setRealSampleRate(double sampleRate)
|
||||||
correctAfterXFrames_ = 0;
|
correctAfterXFrames_ = 0;
|
||||||
else
|
else
|
||||||
correctAfterXFrames_ = round((format_.rate / sampleRate) / (format_.rate / sampleRate - 1.));
|
correctAfterXFrames_ = round((format_.rate / sampleRate) / (format_.rate / sampleRate - 1.));
|
||||||
// LOG(DEBUG) << "Correct after X: " << correctAfterXFrames_ << " (Real rate: " << sampleRate << ", rate: " << format_.rate << ")\n";
|
// LOG(DEBUG) << "Correct after X: " << correctAfterXFrames_ << " (Real rate: " << sampleRate << ", rate: " << format_.rate << ")\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ void Stream::addChunk(msg::PcmChunk* chunk)
|
||||||
while (chunks_.size() * chunk->duration<cs::msec>().count() > 10000)
|
while (chunks_.size() * chunk->duration<cs::msec>().count() > 10000)
|
||||||
chunks_.pop();
|
chunks_.pop();
|
||||||
chunks_.push(shared_ptr<msg::PcmChunk>(chunk));
|
chunks_.push(shared_ptr<msg::PcmChunk>(chunk));
|
||||||
// LOG(DEBUG) << "new chunk: " << chunk->duration<cs::msec>().count() << ", Chunks: " << chunks_.size() << "\n";
|
// LOG(DEBUG) << "new chunk: " << chunk->duration<cs::msec>().count() << ", Chunks: " << chunks_.size() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -145,7 +146,7 @@ cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec
|
||||||
unsigned long read = 0;
|
unsigned long read = 0;
|
||||||
while (read < framesPerBuffer)
|
while (read < framesPerBuffer)
|
||||||
{
|
{
|
||||||
read += chunk_->readFrames(buffer + read*format_.frameSize, framesPerBuffer - read);
|
read += chunk_->readFrames(buffer + read * format_.frameSize, framesPerBuffer - read);
|
||||||
if (chunk_->isEndOfChunk() && !chunks_.try_pop(chunk_, timeout))
|
if (chunk_->isEndOfChunk() && !chunks_.try_pop(chunk_, timeout))
|
||||||
throw 0;
|
throw 0;
|
||||||
}
|
}
|
||||||
|
@ -162,14 +163,14 @@ cs::time_point_clk Stream::getNextPlayerChunk(void* outputBuffer, const cs::usec
|
||||||
char* buffer = (char*)malloc(toRead * format_.frameSize);
|
char* buffer = (char*)malloc(toRead * format_.frameSize);
|
||||||
cs::time_point_clk tp = getNextPlayerChunk(buffer, timeout, toRead);
|
cs::time_point_clk tp = getNextPlayerChunk(buffer, timeout, toRead);
|
||||||
|
|
||||||
float factor = (float)toRead / framesPerBuffer;//(float)(framesPerBuffer*channels_);
|
float factor = (float)toRead / framesPerBuffer; //(float)(framesPerBuffer*channels_);
|
||||||
// if (abs(framesCorrection) > 1)
|
// if (abs(framesCorrection) > 1)
|
||||||
// LOG(INFO) << "correction: " << framesCorrection << ", factor: " << factor << "\n";
|
// LOG(INFO) << "correction: " << framesCorrection << ", factor: " << factor << "\n";
|
||||||
float idx = 0;
|
float idx = 0;
|
||||||
for (size_t n=0; n<framesPerBuffer; ++n)
|
for (size_t n = 0; n < framesPerBuffer; ++n)
|
||||||
{
|
{
|
||||||
size_t index(floor(idx));// = (int)(ceil(n*factor));
|
size_t index(floor(idx)); // = (int)(ceil(n*factor));
|
||||||
memcpy((char*)outputBuffer + n*format_.frameSize, buffer + index*format_.frameSize, format_.frameSize);
|
memcpy((char*)outputBuffer + n * format_.frameSize, buffer + index * format_.frameSize, format_.frameSize);
|
||||||
idx += factor;
|
idx += factor;
|
||||||
}
|
}
|
||||||
free(buffer);
|
free(buffer);
|
||||||
|
@ -207,7 +208,7 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
|
|
||||||
if (!chunk_ && !chunks_.try_pop(chunk_, outputBufferDacTime))
|
if (!chunk_ && !chunks_.try_pop(chunk_, outputBufferDacTime))
|
||||||
{
|
{
|
||||||
//LOG(INFO) << "no chunks available\n";
|
// LOG(INFO) << "no chunks available\n";
|
||||||
sleep_ = cs::usec(0);
|
sleep_ = cs::usec(0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -220,7 +221,7 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
/// age < 0 => play in -age
|
/// age < 0 => play in -age
|
||||||
/// age > 0 => too old
|
/// age > 0 => too old
|
||||||
cs::usec age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start()) - bufferMs_ + outputBufferDacTime;
|
cs::usec age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start()) - bufferMs_ + outputBufferDacTime;
|
||||||
// LOG(INFO) << "age: " << age.count() / 1000 << "\n";
|
// LOG(INFO) << "age: " << age.count() / 1000 << "\n";
|
||||||
if ((sleep_.count() == 0) && (cs::abs(age) > cs::msec(200)))
|
if ((sleep_.count() == 0) && (cs::abs(age) > cs::msec(200)))
|
||||||
{
|
{
|
||||||
LOG(INFO) << "age > 200: " << cs::duration<cs::msec>(age) << "\n";
|
LOG(INFO) << "age > 200: " << cs::duration<cs::msec>(age) << "\n";
|
||||||
|
@ -230,30 +231,33 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
//LOG(DEBUG) << "framesPerBuffer: " << framesPerBuffer << "\tms: " << framesPerBuffer*2 / PLAYER_CHUNK_MS_SIZE << "\t" << PLAYER_CHUNK_SIZE << "\n";
|
// LOG(DEBUG) << "framesPerBuffer: " << framesPerBuffer << "\tms: " << framesPerBuffer*2 / PLAYER_CHUNK_MS_SIZE << "\t" << PLAYER_CHUNK_SIZE << "\n";
|
||||||
cs::nsec bufferDuration = cs::nsec(cs::nsec::rep(framesPerBuffer / format_.nsRate()));
|
cs::nsec bufferDuration = cs::nsec(cs::nsec::rep(framesPerBuffer / format_.nsRate()));
|
||||||
// LOG(DEBUG) << "buffer duration: " << bufferDuration.count() << "\n";
|
// LOG(DEBUG) << "buffer duration: " << bufferDuration.count() << "\n";
|
||||||
|
|
||||||
cs::usec correction = cs::usec(0);
|
cs::usec correction = cs::usec(0);
|
||||||
if (sleep_.count() != 0)
|
if (sleep_.count() != 0)
|
||||||
{
|
{
|
||||||
resetBuffers();
|
resetBuffers();
|
||||||
if (sleep_ < -bufferDuration/2)
|
if (sleep_ < -bufferDuration / 2)
|
||||||
{
|
{
|
||||||
LOG(INFO) << "sleep < -bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " < " << -cs::duration<cs::msec>(bufferDuration)/2 << ", ";
|
LOG(INFO) << "sleep < -bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " < " << -cs::duration<cs::msec>(bufferDuration) / 2 << ", ";
|
||||||
// We're early: not enough chunks_. play silence. Reference chunk_ is the oldest (front) one
|
// We're early: not enough chunks_. play silence. Reference chunk_ is the oldest (front) one
|
||||||
sleep_ = chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - getSilentPlayerChunk(outputBuffer, framesPerBuffer) - bufferMs_ + outputBufferDacTime);
|
sleep_ = chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - getSilentPlayerChunk(outputBuffer, framesPerBuffer) - bufferMs_ +
|
||||||
|
outputBufferDacTime);
|
||||||
LOG(INFO) << "sleep: " << cs::duration<cs::msec>(sleep_) << "\n";
|
LOG(INFO) << "sleep: " << cs::duration<cs::msec>(sleep_) << "\n";
|
||||||
if (sleep_ < -bufferDuration/2)
|
if (sleep_ < -bufferDuration / 2)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (sleep_ > bufferDuration/2)
|
else if (sleep_ > bufferDuration / 2)
|
||||||
{
|
{
|
||||||
LOG(INFO) << "sleep > bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " > " << cs::duration<cs::msec>(bufferDuration)/2 << "\n";
|
LOG(INFO) << "sleep > bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " > " << cs::duration<cs::msec>(bufferDuration) / 2 << "\n";
|
||||||
// We're late: discard oldest chunks
|
// We're late: discard oldest chunks
|
||||||
while (sleep_ > chunk_->duration<cs::usec>())
|
while (sleep_ > chunk_->duration<cs::usec>())
|
||||||
{
|
{
|
||||||
LOG(INFO) << "sleep > chunkDuration: " << cs::duration<cs::msec>(sleep_) << " > " << chunk_->duration<cs::msec>().count() << ", chunks: " << chunks_.size() << ", out: " << cs::duration<cs::msec>(outputBufferDacTime) << ", needed: " << cs::duration<cs::msec>(bufferDuration) << "\n";
|
LOG(INFO) << "sleep > chunkDuration: " << cs::duration<cs::msec>(sleep_) << " > " << chunk_->duration<cs::msec>().count()
|
||||||
|
<< ", chunks: " << chunks_.size() << ", out: " << cs::duration<cs::msec>(outputBufferDacTime)
|
||||||
|
<< ", needed: " << cs::duration<cs::msec>(bufferDuration) << "\n";
|
||||||
sleep_ = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start() - bufferMs_ + outputBufferDacTime);
|
sleep_ = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start() - bufferMs_ + outputBufferDacTime);
|
||||||
if (!chunks_.try_pop(chunk_, outputBufferDacTime))
|
if (!chunks_.try_pop(chunk_, outputBufferDacTime))
|
||||||
{
|
{
|
||||||
|
@ -285,16 +289,18 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
}
|
}
|
||||||
|
|
||||||
// framesCorrection = number of frames to be read more or less to get in-sync
|
// framesCorrection = number of frames to be read more or less to get in-sync
|
||||||
long framesCorrection = correction.count()*format_.usRate();
|
long framesCorrection = correction.count() * format_.usRate();
|
||||||
|
|
||||||
// sample rate correction
|
// sample rate correction
|
||||||
if ((correctAfterXFrames_ != 0) && (playedFrames_ >= (unsigned long)abs(correctAfterXFrames_)))
|
if ((correctAfterXFrames_ != 0) && (playedFrames_ >= (unsigned long)abs(correctAfterXFrames_)))
|
||||||
{
|
{
|
||||||
framesCorrection += (correctAfterXFrames_ > 0)?1:-1;
|
framesCorrection += (correctAfterXFrames_ > 0) ? 1 : -1;
|
||||||
playedFrames_ -= abs(correctAfterXFrames_);
|
playedFrames_ -= abs(correctAfterXFrames_);
|
||||||
}
|
}
|
||||||
|
|
||||||
age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - getNextPlayerChunk(outputBuffer, outputBufferDacTime, framesPerBuffer, framesCorrection) - bufferMs_ + outputBufferDacTime);
|
age = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() -
|
||||||
|
getNextPlayerChunk(outputBuffer, outputBufferDacTime, framesPerBuffer, framesCorrection) - bufferMs_ +
|
||||||
|
outputBufferDacTime);
|
||||||
|
|
||||||
setRealSampleRate(format_.rate);
|
setRealSampleRate(format_.rate);
|
||||||
if (sleep_.count() == 0)
|
if (sleep_.count() == 0)
|
||||||
|
@ -315,8 +321,8 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
setRealSampleRate(format_.rate + format_.rate / 1000);
|
setRealSampleRate(format_.rate + format_.rate / 1000);
|
||||||
}
|
}
|
||||||
*/ }
|
*/ }
|
||||||
else if (shortBuffer_.full())
|
else if (shortBuffer_.full())
|
||||||
{
|
{
|
||||||
if (cs::usec(abs(shortMedian_)) > cs::msec(5))
|
if (cs::usec(abs(shortMedian_)) > cs::msec(5))
|
||||||
{
|
{
|
||||||
LOG(INFO) << "pShortBuffer->full() && (abs(shortMedian_) > 5): " << shortMedian_ << "\n";
|
LOG(INFO) << "pShortBuffer->full() && (abs(shortMedian_) > 5): " << shortMedian_ << "\n";
|
||||||
|
@ -327,11 +333,11 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
setRealSampleRate(format_.rate + -shortMedian_ / 100);
|
setRealSampleRate(format_.rate + -shortMedian_ / 100);
|
||||||
}
|
}
|
||||||
*/ }
|
*/ }
|
||||||
else if (miniBuffer_.full() && (cs::usec(abs(miniBuffer_.median())) > cs::msec(50)))
|
else if (miniBuffer_.full() && (cs::usec(abs(miniBuffer_.median())) > cs::msec(50)))
|
||||||
{
|
{
|
||||||
LOG(INFO) << "pMiniBuffer->full() && (abs(pMiniBuffer->mean()) > 50): " << miniBuffer_.median() << "\n";
|
LOG(INFO) << "pMiniBuffer->full() && (abs(pMiniBuffer->mean()) > 50): " << miniBuffer_.median() << "\n";
|
||||||
sleep_ = cs::usec((cs::msec::rep)miniBuffer_.mean());
|
sleep_ = cs::usec((cs::msec::rep)miniBuffer_.mean());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sleep_.count() != 0)
|
if (sleep_.count() != 0)
|
||||||
|
@ -341,7 +347,8 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
if (lastAge != msAge)
|
if (lastAge != msAge)
|
||||||
{
|
{
|
||||||
lastAge = msAge;
|
lastAge = msAge;
|
||||||
LOG(INFO) << "Sleep " << cs::duration<cs::msec>(sleep_) << ", age: " << msAge << ", bufferDuration: " << cs::duration<cs::msec>(bufferDuration) << "\n";
|
LOG(INFO) << "Sleep " << cs::duration<cs::msec>(sleep_) << ", age: " << msAge << ", bufferDuration: " << cs::duration<cs::msec>(bufferDuration)
|
||||||
|
<< "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (shortBuffer_.full())
|
else if (shortBuffer_.full())
|
||||||
|
@ -361,17 +368,16 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
|
||||||
lastUpdate_ = now;
|
lastUpdate_ = now;
|
||||||
median_ = buffer_.median();
|
median_ = buffer_.median();
|
||||||
shortMedian_ = shortBuffer_.median();
|
shortMedian_ = shortBuffer_.median();
|
||||||
LOG(INFO) << "Chunk: " << age.count()/100 << "\t" << miniBuffer_.median()/100 << "\t" << shortMedian_/100 << "\t" << median_/100 << "\t" << buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n";
|
LOG(INFO) << "Chunk: " << age.count() / 100 << "\t" << miniBuffer_.median() / 100 << "\t" << shortMedian_ / 100 << "\t" << median_ / 100 << "\t"
|
||||||
// LOG(INFO) << "Chunk: " << age.count()/1000 << "\t" << miniBuffer_.median()/1000 << "\t" << shortMedian_/1000 << "\t" << median_/1000 << "\t" << buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n";
|
<< buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n";
|
||||||
|
// LOG(INFO) << "Chunk: " << age.count()/1000 << "\t" << miniBuffer_.median()/1000 << "\t" << shortMedian_/1000 << "\t" << median_/1000 << "\t"
|
||||||
|
//<< buffer_.size() << "\t" << cs::duration<cs::msec>(outputBufferDacTime) << "\n";
|
||||||
}
|
}
|
||||||
return (abs(cs::duration<cs::msec>(age)) < 500);
|
return (abs(cs::duration<cs::msec>(age)) < 500);
|
||||||
}
|
}
|
||||||
catch(int e)
|
catch (int e)
|
||||||
{
|
{
|
||||||
sleep_ = cs::usec(0);
|
sleep_ = cs::usec(0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -25,13 +25,13 @@
|
||||||
//#include <chrono>
|
//#include <chrono>
|
||||||
//#include "common/timeUtils.h"
|
//#include "common/timeUtils.h"
|
||||||
|
|
||||||
#include <deque>
|
#include "common/queue.h"
|
||||||
#include <memory>
|
#include "common/sampleFormat.h"
|
||||||
#include "doubleBuffer.h"
|
#include "doubleBuffer.h"
|
||||||
#include "message/message.h"
|
#include "message/message.h"
|
||||||
#include "message/pcmChunk.h"
|
#include "message/pcmChunk.h"
|
||||||
#include "common/sampleFormat.h"
|
#include <deque>
|
||||||
#include "common/queue.h"
|
#include <memory>
|
||||||
|
|
||||||
|
|
||||||
/// Time synchronized audio stream
|
/// Time synchronized audio stream
|
||||||
|
@ -67,7 +67,7 @@ private:
|
||||||
chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long framesPerBuffer, long framesCorrection);
|
chronos::time_point_clk getNextPlayerChunk(void* outputBuffer, const chronos::usec& timeout, unsigned long framesPerBuffer, long framesCorrection);
|
||||||
chronos::time_point_clk getSilentPlayerChunk(void* outputBuffer, unsigned long framesPerBuffer);
|
chronos::time_point_clk getSilentPlayerChunk(void* outputBuffer, unsigned long framesPerBuffer);
|
||||||
chronos::time_point_clk seek(long ms);
|
chronos::time_point_clk seek(long ms);
|
||||||
// time_point_ms seekTo(const time_point_ms& to);
|
// time_point_ms seekTo(const time_point_ms& to);
|
||||||
void updateBuffers(int age);
|
void updateBuffers(int age);
|
||||||
void resetBuffers();
|
void resetBuffers();
|
||||||
void setRealSampleRate(double sampleRate);
|
void setRealSampleRate(double sampleRate);
|
||||||
|
@ -77,7 +77,7 @@ private:
|
||||||
chronos::usec sleep_;
|
chronos::usec sleep_;
|
||||||
|
|
||||||
Queue<std::shared_ptr<msg::PcmChunk>> chunks_;
|
Queue<std::shared_ptr<msg::PcmChunk>> chunks_;
|
||||||
// DoubleBuffer<chronos::usec::rep> cardBuffer;
|
// DoubleBuffer<chronos::usec::rep> cardBuffer;
|
||||||
DoubleBuffer<chronos::usec::rep> miniBuffer_;
|
DoubleBuffer<chronos::usec::rep> miniBuffer_;
|
||||||
DoubleBuffer<chronos::usec::rep> buffer_;
|
DoubleBuffer<chronos::usec::rep> buffer_;
|
||||||
DoubleBuffer<chronos::usec::rep> shortBuffer_;
|
DoubleBuffer<chronos::usec::rep> shortBuffer_;
|
||||||
|
@ -94,5 +94,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -28,8 +28,8 @@ TimeProvider::TimeProvider() : diffToServer_(0)
|
||||||
|
|
||||||
void TimeProvider::setDiff(const tv& c2s, const tv& s2c)
|
void TimeProvider::setDiff(const tv& c2s, const tv& s2c)
|
||||||
{
|
{
|
||||||
// tv latency = c2s - s2c;
|
// tv latency = c2s - s2c;
|
||||||
// double diff = (latency.sec * 1000. + latency.usec / 1000.) / 2.;
|
// double diff = (latency.sec * 1000. + latency.usec / 1000.) / 2.;
|
||||||
double diff = ((double)c2s.sec / 2. - (double)s2c.sec / 2.) * 1000. + ((double)c2s.usec / 2. - (double)s2c.usec / 2.) / 1000.;
|
double diff = ((double)c2s.sec / 2. - (double)s2c.sec / 2.) * 1000. + ((double)c2s.usec / 2. - (double)s2c.usec / 2.) / 1000.;
|
||||||
setDiffToServer(diff);
|
setDiffToServer(diff);
|
||||||
}
|
}
|
||||||
|
@ -45,14 +45,14 @@ void TimeProvider::setDiffToServer(double ms)
|
||||||
if (!diffBuffer_.empty() && (std::abs(now.tv_sec - lastTimeSync) > 60))
|
if (!diffBuffer_.empty() && (std::abs(now.tv_sec - lastTimeSync) > 60))
|
||||||
{
|
{
|
||||||
LOG(INFO) << "Last time sync older than a minute. Clearing time buffer\n";
|
LOG(INFO) << "Last time sync older than a minute. Clearing time buffer\n";
|
||||||
diffToServer_ = ms*1000;
|
diffToServer_ = ms * 1000;
|
||||||
diffBuffer_.clear();
|
diffBuffer_.clear();
|
||||||
}
|
}
|
||||||
lastTimeSync = now.tv_sec;
|
lastTimeSync = now.tv_sec;
|
||||||
|
|
||||||
diffBuffer_.add(ms*1000);
|
diffBuffer_.add(ms * 1000);
|
||||||
diffToServer_ = diffBuffer_.median(3);
|
diffToServer_ = diffBuffer_.median(3);
|
||||||
// LOG(INFO) << "setDiffToServer: " << ms << ", diff: " << diffToServer_ / 1000.f << "\n";
|
// LOG(INFO) << "setDiffToServer: " << ms << ", diff: " << diffToServer_ / 1000.f << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -61,4 +61,3 @@ long TimeProvider::getPercentileDiffToServer(size_t percentile)
|
||||||
return diffBuffer.percentile(percentile);
|
return diffBuffer.percentile(percentile);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,11 +19,11 @@
|
||||||
#ifndef TIME_PROVIDER_H
|
#ifndef TIME_PROVIDER_H
|
||||||
#define TIME_PROVIDER_H
|
#define TIME_PROVIDER_H
|
||||||
|
|
||||||
#include <atomic>
|
#include "common/timeDefs.h"
|
||||||
#include <chrono>
|
|
||||||
#include "doubleBuffer.h"
|
#include "doubleBuffer.h"
|
||||||
#include "message/message.h"
|
#include "message/message.h"
|
||||||
#include "common/timeDefs.h"
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
|
||||||
/// Provides local and server time
|
/// Provides local and server time
|
||||||
|
@ -44,18 +44,18 @@ public:
|
||||||
void setDiffToServer(double ms);
|
void setDiffToServer(double ms);
|
||||||
void setDiff(const tv& c2s, const tv& s2c);
|
void setDiff(const tv& c2s, const tv& s2c);
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
inline T getDiffToServer() const
|
inline T getDiffToServer() const
|
||||||
{
|
{
|
||||||
return std::chrono::duration_cast<T>(chronos::usec(diffToServer_));
|
return std::chrono::duration_cast<T>(chronos::usec(diffToServer_));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* chronos::usec::rep getDiffToServer();
|
/* chronos::usec::rep getDiffToServer();
|
||||||
chronos::usec::rep getPercentileDiffToServer(size_t percentile);
|
chronos::usec::rep getPercentileDiffToServer(size_t percentile);
|
||||||
long getDiffToServerMs();
|
long getDiffToServerMs();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
static T sinceEpoche(const chronos::time_point_clk& point)
|
static T sinceEpoche(const chronos::time_point_clk& point)
|
||||||
{
|
{
|
||||||
return std::chrono::duration_cast<T>(point.time_since_epoch());
|
return std::chrono::duration_cast<T>(point.time_since_epoch());
|
||||||
|
@ -87,5 +87,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# This file is part of snapcast
|
# This file is part of snapcast
|
||||||
# Copyright (C) 2014-2018 Johannes Pohl
|
# Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,27 +18,24 @@
|
||||||
|
|
||||||
#include "daemon.h"
|
#include "daemon.h"
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <syslog.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#include <grp.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
#include "common/strCompat.h"
|
#include "common/strCompat.h"
|
||||||
#include "common/utils/file_utils.h"
|
|
||||||
#include "common/utils.h"
|
#include "common/utils.h"
|
||||||
|
#include "common/utils/file_utils.h"
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <grp.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
|
||||||
Daemon::Daemon(const std::string& user, const std::string& group, const std::string& pidfile) :
|
Daemon::Daemon(const std::string& user, const std::string& group, const std::string& pidfile)
|
||||||
pidFilehandle_(-1),
|
: pidFilehandle_(-1), user_(user), group_(group), pidfile_(pidfile)
|
||||||
user_(user),
|
|
||||||
group_(group),
|
|
||||||
pidfile_(pidfile)
|
|
||||||
{
|
{
|
||||||
if (pidfile.empty() || pidfile.find('/') == std::string::npos)
|
if (pidfile.empty() || pidfile.find('/') == std::string::npos)
|
||||||
throw SnapException("invalid pid file \"" + pidfile + "\"");
|
throw SnapException("invalid pid file \"" + pidfile + "\"");
|
||||||
|
@ -58,8 +55,8 @@ void Daemon::daemonize()
|
||||||
utils::file::mkdirRecursive(pidfileDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
utils::file::mkdirRecursive(pidfileDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||||
|
|
||||||
/// Ensure only one copy
|
/// Ensure only one copy
|
||||||
pidFilehandle_ = open(pidfile_.c_str(), O_RDWR|O_CREAT, 0644);
|
pidFilehandle_ = open(pidfile_.c_str(), O_RDWR | O_CREAT, 0644);
|
||||||
if (pidFilehandle_ == -1 )
|
if (pidFilehandle_ == -1)
|
||||||
{
|
{
|
||||||
/// Couldn't open lock file
|
/// Couldn't open lock file
|
||||||
throw SnapException("Could not open PID lock file \"" + pidfile_ + "\"");
|
throw SnapException("Could not open PID lock file \"" + pidfile_ + "\"");
|
||||||
|
@ -68,13 +65,13 @@ void Daemon::daemonize()
|
||||||
uid_t user_uid = (uid_t)-1;
|
uid_t user_uid = (uid_t)-1;
|
||||||
gid_t user_gid = (gid_t)-1;
|
gid_t user_gid = (gid_t)-1;
|
||||||
std::string user_name;
|
std::string user_name;
|
||||||
//#ifdef FREEBSD
|
//#ifdef FREEBSD
|
||||||
// bool had_group = false;
|
// bool had_group = false;
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
if (!user_.empty())
|
if (!user_.empty())
|
||||||
{
|
{
|
||||||
struct passwd *pwd = getpwnam(user_.c_str());
|
struct passwd* pwd = getpwnam(user_.c_str());
|
||||||
if (pwd == nullptr)
|
if (pwd == nullptr)
|
||||||
throw SnapException("no such user \"" + user_ + "\"");
|
throw SnapException("no such user \"" + user_ + "\"");
|
||||||
user_uid = pwd->pw_uid;
|
user_uid = pwd->pw_uid;
|
||||||
|
@ -86,13 +83,13 @@ void Daemon::daemonize()
|
||||||
|
|
||||||
if (!group_.empty())
|
if (!group_.empty())
|
||||||
{
|
{
|
||||||
struct group *grp = getgrnam(group_.c_str());
|
struct group* grp = getgrnam(group_.c_str());
|
||||||
if (grp == nullptr)
|
if (grp == nullptr)
|
||||||
throw SnapException("no such group \"" + group_ + "\"");
|
throw SnapException("no such group \"" + group_ + "\"");
|
||||||
user_gid = grp->gr_gid;
|
user_gid = grp->gr_gid;
|
||||||
//#ifdef FREEBSD
|
//#ifdef FREEBSD
|
||||||
// had_group = true;
|
// had_group = true;
|
||||||
//#endif
|
//#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chown(pidfile_.c_str(), user_uid, user_gid) == -1)
|
if (chown(pidfile_.c_str(), user_uid, user_gid) == -1)
|
||||||
|
@ -105,14 +102,14 @@ void Daemon::daemonize()
|
||||||
if (user_gid != (gid_t)-1 && user_gid != getgid() && setgid(user_gid) == -1)
|
if (user_gid != (gid_t)-1 && user_gid != getgid() && setgid(user_gid) == -1)
|
||||||
throw SnapException("Failed to set group " + cpt::to_string((int)user_gid));
|
throw SnapException("Failed to set group " + cpt::to_string((int)user_gid));
|
||||||
|
|
||||||
//#if defined(FREEBSD) && !defined(MACOS)
|
//#if defined(FREEBSD) && !defined(MACOS)
|
||||||
//#ifdef FREEBSD
|
//#ifdef FREEBSD
|
||||||
/// init supplementary groups
|
/// init supplementary groups
|
||||||
/// (must be done before we change our uid)
|
/// (must be done before we change our uid)
|
||||||
/// no need to set the new user's supplementary groups if we are already this user
|
/// no need to set the new user's supplementary groups if we are already this user
|
||||||
// if (!had_group && user_uid != getuid() && initgroups(user_name, user_gid) == -1)
|
// if (!had_group && user_uid != getuid() && initgroups(user_name, user_gid) == -1)
|
||||||
// throw SnapException("Failed to set supplementary groups of user \"" + user + "\"");
|
// throw SnapException("Failed to set supplementary groups of user \"" + user + "\"");
|
||||||
//#endif
|
//#endif
|
||||||
/// set uid
|
/// set uid
|
||||||
if (user_uid != (uid_t)-1 && user_uid != getuid() && setuid(user_uid) == -1)
|
if (user_uid != (uid_t)-1 && user_uid != getuid() && setuid(user_uid) == -1)
|
||||||
throw SnapException("Failed to set user " + user_);
|
throw SnapException("Failed to set user " + user_);
|
||||||
|
@ -166,6 +163,3 @@ void Daemon::daemonize()
|
||||||
close(STDOUT_FILENO);
|
close(STDOUT_FILENO);
|
||||||
close(STDERR_FILENO);
|
close(STDERR_FILENO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -62,10 +62,7 @@ protected:
|
||||||
writeVal(stream, payload, payloadSize);
|
writeVal(stream, payload, payloadSize);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
#ifndef HELLO_MSG_H
|
#ifndef HELLO_MSG_H
|
||||||
#define HELLO_MSG_H
|
#define HELLO_MSG_H
|
||||||
|
|
||||||
#include "jsonMessage.h"
|
|
||||||
#include "common/utils.h"
|
|
||||||
#include "common/strCompat.h"
|
#include "common/strCompat.h"
|
||||||
|
#include "common/utils.h"
|
||||||
|
#include "jsonMessage.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,10 +108,7 @@ public:
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,8 +19,8 @@
|
||||||
#ifndef JSON_MESSAGE_H
|
#ifndef JSON_MESSAGE_H
|
||||||
#define JSON_MESSAGE_H
|
#define JSON_MESSAGE_H
|
||||||
|
|
||||||
#include "message.h"
|
|
||||||
#include "common/json.hpp"
|
#include "common/json.hpp"
|
||||||
|
#include "message.h"
|
||||||
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
@ -61,7 +61,7 @@ protected:
|
||||||
writeVal(stream, msg.dump());
|
writeVal(stream, msg.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
T get(const std::string& what, const T& def) const
|
T get(const std::string& what, const T& def) const
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -70,16 +70,13 @@ protected:
|
||||||
return def;
|
return def;
|
||||||
return msg[what].get<T>();
|
return msg[what].get<T>();
|
||||||
}
|
}
|
||||||
catch(...)
|
catch (...)
|
||||||
{
|
{
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,14 +19,14 @@
|
||||||
#ifndef MESSAGE_H
|
#ifndef MESSAGE_H
|
||||||
#define MESSAGE_H
|
#define MESSAGE_H
|
||||||
|
|
||||||
|
#include "common/endian.hpp"
|
||||||
|
#include "common/timeDefs.h"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
#include <vector>
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include "common/endian.hpp"
|
#include <vector>
|
||||||
#include "common/timeDefs.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
template<typename CharT, typename TraitsT = std::char_traits<CharT> >
|
template<typename CharT, typename TraitsT = std::char_traits<CharT> >
|
||||||
|
@ -74,8 +74,8 @@ struct tv
|
||||||
sec = t.tv_sec;
|
sec = t.tv_sec;
|
||||||
usec = t.tv_usec;
|
usec = t.tv_usec;
|
||||||
}
|
}
|
||||||
tv(timeval tv) : sec(tv.tv_sec), usec(tv.tv_usec) {};
|
tv(timeval tv) : sec(tv.tv_sec), usec(tv.tv_usec){};
|
||||||
tv(int32_t _sec, int32_t _usec) : sec(_sec), usec(_usec) {};
|
tv(int32_t _sec, int32_t _usec) : sec(_sec), usec(_usec){};
|
||||||
|
|
||||||
int32_t sec;
|
int32_t sec;
|
||||||
int32_t usec;
|
int32_t usec;
|
||||||
|
@ -178,7 +178,7 @@ struct BaseMessage
|
||||||
|
|
||||||
virtual uint32_t getSize() const
|
virtual uint32_t getSize() const
|
||||||
{
|
{
|
||||||
return 3*sizeof(uint16_t) + 2*sizeof(tv) + sizeof(uint32_t);
|
return 3 * sizeof(uint16_t) + 2 * sizeof(tv) + sizeof(uint32_t);
|
||||||
};
|
};
|
||||||
|
|
||||||
uint16_t type;
|
uint16_t type;
|
||||||
|
@ -191,7 +191,7 @@ struct BaseMessage
|
||||||
protected:
|
protected:
|
||||||
void writeVal(std::ostream& stream, const bool& val) const
|
void writeVal(std::ostream& stream, const bool& val) const
|
||||||
{
|
{
|
||||||
char c = val?1:0;
|
char c = val ? 1 : 0;
|
||||||
writeVal(stream, c);
|
writeVal(stream, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,9 +290,7 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
virtual void doserialize(std::ostream& stream) const
|
virtual void doserialize(std::ostream& stream) const {};
|
||||||
{
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -306,9 +304,6 @@ struct SerializedMessage
|
||||||
BaseMessage message;
|
BaseMessage message;
|
||||||
char* buffer;
|
char* buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
#ifndef PCM_CHUNK_H
|
#ifndef PCM_CHUNK_H
|
||||||
#define PCM_CHUNK_H
|
#define PCM_CHUNK_H
|
||||||
|
|
||||||
#include <chrono>
|
#include "common/sampleFormat.h"
|
||||||
#include "message.h"
|
#include "message.h"
|
||||||
#include "wireChunk.h"
|
#include "wireChunk.h"
|
||||||
#include "common/sampleFormat.h"
|
#include <chrono>
|
||||||
|
|
||||||
|
|
||||||
namespace msg
|
namespace msg
|
||||||
|
@ -36,17 +36,11 @@ namespace msg
|
||||||
class PcmChunk : public WireChunk
|
class PcmChunk : public WireChunk
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PcmChunk(const SampleFormat& sampleFormat, size_t ms) :
|
PcmChunk(const SampleFormat& sampleFormat, size_t ms) : WireChunk(sampleFormat.rate * sampleFormat.frameSize * ms / 1000), format(sampleFormat), idx_(0)
|
||||||
WireChunk(sampleFormat.rate*sampleFormat.frameSize*ms / 1000),
|
|
||||||
format(sampleFormat),
|
|
||||||
idx_(0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
PcmChunk(const PcmChunk& pcmChunk) :
|
PcmChunk(const PcmChunk& pcmChunk) : WireChunk(pcmChunk), format(pcmChunk.format), idx_(0)
|
||||||
WireChunk(pcmChunk),
|
|
||||||
format(pcmChunk.format),
|
|
||||||
idx_(0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,17 +54,18 @@ public:
|
||||||
|
|
||||||
int readFrames(void* outputBuffer, size_t frameCount)
|
int readFrames(void* outputBuffer, size_t frameCount)
|
||||||
{
|
{
|
||||||
//logd << "read: " << frameCount << ", total: " << (wireChunk->length / format.frameSize) << ", idx: " << idx;// << std::endl;
|
// logd << "read: " << frameCount << ", total: " << (wireChunk->length / format.frameSize) << ", idx: " << idx;// << std::endl;
|
||||||
int result = frameCount;
|
int result = frameCount;
|
||||||
if (idx_ + frameCount > (payloadSize / format.frameSize))
|
if (idx_ + frameCount > (payloadSize / format.frameSize))
|
||||||
result = (payloadSize / format.frameSize) - idx_;
|
result = (payloadSize / format.frameSize) - idx_;
|
||||||
|
|
||||||
//logd << ", from: " << format.frameSize*idx << ", to: " << format.frameSize*idx + format.frameSize*result;
|
// logd << ", from: " << format.frameSize*idx << ", to: " << format.frameSize*idx + format.frameSize*result;
|
||||||
if (outputBuffer != NULL)
|
if (outputBuffer != NULL)
|
||||||
memcpy((char*)outputBuffer, (char*)(payload) + format.frameSize*idx_, format.frameSize*result);
|
memcpy((char*)outputBuffer, (char*)(payload) + format.frameSize * idx_, format.frameSize * result);
|
||||||
|
|
||||||
idx_ += result;
|
idx_ += result;
|
||||||
//logd << ", new idx: " << idx << ", result: " << result << ", wireChunk->length: " << wireChunk->length << ", format.frameSize: " << format.frameSize << "\n";//std::endl;
|
// logd << ", new idx: " << idx << ", result: " << result << ", wireChunk->length: " << wireChunk->length << ", format.frameSize: " << format.frameSize
|
||||||
|
// << "\n";//std::endl;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,11 +84,8 @@ public:
|
||||||
|
|
||||||
virtual chronos::time_point_clk start() const
|
virtual chronos::time_point_clk start() const
|
||||||
{
|
{
|
||||||
return chronos::time_point_clk(
|
return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec) +
|
||||||
chronos::sec(timestamp.sec) +
|
chronos::usec((chronos::usec::rep)(1000000. * ((double)idx_ / (double)format.rate))));
|
||||||
chronos::usec(timestamp.usec) +
|
|
||||||
chronos::usec((chronos::usec::rep)(1000000. * ((double)idx_ / (double)format.rate)))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline chronos::time_point_clk end() const
|
inline chronos::time_point_clk end() const
|
||||||
|
@ -101,13 +93,13 @@ public:
|
||||||
return start() + durationLeft<chronos::usec>();
|
return start() + durationLeft<chronos::usec>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
inline T duration() const
|
inline T duration() const
|
||||||
{
|
{
|
||||||
return std::chrono::duration_cast<T>(chronos::nsec((chronos::nsec::rep)(1000000 * getFrameCount() / format.msRate())));
|
return std::chrono::duration_cast<T>(chronos::nsec((chronos::nsec::rep)(1000000 * getFrameCount() / format.msRate())));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
inline T durationLeft() const
|
inline T durationLeft() const
|
||||||
{
|
{
|
||||||
return std::chrono::duration_cast<T>(chronos::nsec((chronos::nsec::rep)(1000000 * (getFrameCount() - idx_) / format.msRate())));
|
return std::chrono::duration_cast<T>(chronos::nsec((chronos::nsec::rep)(1000000 * (getFrameCount() - idx_) / format.msRate())));
|
||||||
|
@ -133,9 +125,6 @@ public:
|
||||||
private:
|
private:
|
||||||
uint32_t idx_;
|
uint32_t idx_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -82,10 +82,7 @@ public:
|
||||||
msg["muted"] = muted;
|
msg["muted"] = muted;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -69,10 +69,7 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -55,10 +55,7 @@ protected:
|
||||||
writeVal(stream, latency.usec);
|
writeVal(stream, latency.usec);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,14 +19,14 @@
|
||||||
#ifndef WIRE_CHUNK_H
|
#ifndef WIRE_CHUNK_H
|
||||||
#define WIRE_CHUNK_H
|
#define WIRE_CHUNK_H
|
||||||
|
|
||||||
|
#include "common/timeDefs.h"
|
||||||
|
#include "message.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "message.h"
|
|
||||||
#include "common/timeDefs.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace msg
|
namespace msg
|
||||||
|
@ -84,10 +84,7 @@ protected:
|
||||||
writeVal(stream, payload, payloadSize);
|
writeVal(stream, payload, payloadSize);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,24 +19,23 @@
|
||||||
#ifndef QUEUE_H
|
#ifndef QUEUE_H
|
||||||
#define QUEUE_H
|
#define QUEUE_H
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <thread>
|
|
||||||
#include <mutex>
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class Queue
|
class Queue
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
T pop()
|
T pop()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> mlock(mutex_);
|
std::unique_lock<std::mutex> mlock(mutex_);
|
||||||
while (queue_.empty())
|
while (queue_.empty())
|
||||||
cond_.wait(mlock);
|
cond_.wait(mlock);
|
||||||
|
|
||||||
// std::lock_guard<std::mutex> lock(mutex_);
|
// std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto val = queue_.front();
|
auto val = queue_.front();
|
||||||
queue_.pop_front();
|
queue_.pop_front();
|
||||||
return val;
|
return val;
|
||||||
|
@ -148,7 +147,7 @@ public:
|
||||||
return (size() == 0);
|
return (size() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Queue()=default;
|
Queue() = default;
|
||||||
Queue(const Queue&) = delete; // disable copying
|
Queue(const Queue&) = delete; // disable copying
|
||||||
Queue& operator=(const Queue&) = delete; // disable assignment
|
Queue& operator=(const Queue&) = delete; // disable assignment
|
||||||
|
|
||||||
|
@ -161,5 +160,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,15 +16,15 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "sampleFormat.h"
|
|
||||||
#include "common/strCompat.h"
|
|
||||||
#include "common/utils/string_utils.h"
|
|
||||||
#include "common/utils.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "common/strCompat.h"
|
||||||
|
#include "common/utils.h"
|
||||||
|
#include "common/utils/string_utils.h"
|
||||||
|
#include "sampleFormat.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -60,16 +60,13 @@ void SampleFormat::setFormat(const std::string& format)
|
||||||
std::vector<std::string> strs;
|
std::vector<std::string> strs;
|
||||||
strs = utils::string::split(format, ':');
|
strs = utils::string::split(format, ':');
|
||||||
if (strs.size() == 3)
|
if (strs.size() == 3)
|
||||||
setFormat(
|
setFormat(cpt::stoul(strs[0]), cpt::stoul(strs[1]), cpt::stoul(strs[2]));
|
||||||
cpt::stoul(strs[0]),
|
|
||||||
cpt::stoul(strs[1]),
|
|
||||||
cpt::stoul(strs[2]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void SampleFormat::setFormat(uint32_t rate, uint16_t bits, uint16_t channels)
|
void SampleFormat::setFormat(uint32_t rate, uint16_t bits, uint16_t channels)
|
||||||
{
|
{
|
||||||
//needs something like:
|
// needs something like:
|
||||||
// 24_4 = 3 bytes, padded to 4
|
// 24_4 = 3 bytes, padded to 4
|
||||||
// 32 = 4 bytes
|
// 32 = 4 bytes
|
||||||
this->rate = rate;
|
this->rate = rate;
|
||||||
|
@ -78,8 +75,6 @@ void SampleFormat::setFormat(uint32_t rate, uint16_t bits, uint16_t channels)
|
||||||
sampleSize = bits / 8;
|
sampleSize = bits / 8;
|
||||||
if (bits == 24)
|
if (bits == 24)
|
||||||
sampleSize = 4;
|
sampleSize = 4;
|
||||||
frameSize = channels*sampleSize;
|
frameSize = channels * sampleSize;
|
||||||
// LOG(DEBUG) << "SampleFormat: " << rate << ":" << bits << ":" << channels << "\n";
|
// LOG(DEBUG) << "SampleFormat: " << rate << ":" << bits << ":" << channels << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -31,7 +31,8 @@
|
||||||
* 1 frame represents 1 analog sample from all channels; here we have 2 channels, and so:
|
* 1 frame represents 1 analog sample from all channels; here we have 2 channels, and so:
|
||||||
* 1 frame = (num_channels) * (1 sample in bytes) = (2 channels) * (2 bytes (16 bits) per sample) = 4 bytes (32 bits)
|
* 1 frame = (num_channels) * (1 sample in bytes) = (2 channels) * (2 bytes (16 bits) per sample) = 4 bytes (32 bits)
|
||||||
* To sustain 2x 44.1 KHz analog rate - the system must be capable of data transfer rate, in Bytes/sec:
|
* To sustain 2x 44.1 KHz analog rate - the system must be capable of data transfer rate, in Bytes/sec:
|
||||||
* Bps_rate = (num_channels) * (1 sample in bytes) * (analog_rate) = (1 frame) * (analog_rate) = ( 2 channels ) * (2 bytes/sample) * (44100 samples/sec) = 2*2*44100 = 176400 Bytes/sec (link to formula img)
|
* Bps_rate = (num_channels) * (1 sample in bytes) * (analog_rate) = (1 frame) * (analog_rate) = ( 2 channels ) * (2 bytes/sample) * (44100 samples/sec) =
|
||||||
|
* 2*2*44100 = 176400 Bytes/sec (link to formula img)
|
||||||
*/
|
*/
|
||||||
class SampleFormat
|
class SampleFormat
|
||||||
{
|
{
|
||||||
|
@ -57,20 +58,19 @@ public:
|
||||||
|
|
||||||
inline double msRate() const
|
inline double msRate() const
|
||||||
{
|
{
|
||||||
return (double)rate/1000.;
|
return (double)rate / 1000.;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double usRate() const
|
inline double usRate() const
|
||||||
{
|
{
|
||||||
return (double)rate/1000000.;
|
return (double)rate / 1000000.;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double nsRate() const
|
inline double nsRate() const
|
||||||
{
|
{
|
||||||
return (double)rate/1000000000.;
|
return (double)rate / 1000000000.;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -27,7 +27,7 @@ extern volatile sig_atomic_t g_terminated;
|
||||||
void signal_handler(int sig)
|
void signal_handler(int sig)
|
||||||
{
|
{
|
||||||
|
|
||||||
switch(sig)
|
switch (sig)
|
||||||
{
|
{
|
||||||
case SIGHUP:
|
case SIGHUP:
|
||||||
syslog(LOG_WARNING, "Received SIGHUP signal.");
|
syslog(LOG_WARNING, "Received SIGHUP signal.");
|
||||||
|
@ -47,5 +47,3 @@ void signal_handler(int sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,13 +19,15 @@
|
||||||
#ifndef SNAP_EXCEPTION_H
|
#ifndef SNAP_EXCEPTION_H
|
||||||
#define SNAP_EXCEPTION_H
|
#define SNAP_EXCEPTION_H
|
||||||
|
|
||||||
|
#include <cstring> // std::strlen, std::strcpy
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstring> // std::strlen, std::strcpy
|
|
||||||
|
|
||||||
// text_exception uses a dynamically-allocated internal c-string for what():
|
// text_exception uses a dynamically-allocated internal c-string for what():
|
||||||
class SnapException : public std::exception {
|
class SnapException : public std::exception
|
||||||
|
{
|
||||||
char* text_;
|
char* text_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SnapException(const char* text)
|
SnapException(const char* text)
|
||||||
{
|
{
|
||||||
|
@ -78,5 +80,3 @@ public:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,105 +2,104 @@
|
||||||
#define COMPAT_H
|
#define COMPAT_H
|
||||||
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <clocale>
|
#include <clocale>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#ifdef NO_CPP11_STRING
|
#ifdef NO_CPP11_STRING
|
||||||
#include <sstream>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cmath>
|
|
||||||
#include <climits>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
#include <climits>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
namespace cpt
|
namespace cpt
|
||||||
{
|
{
|
||||||
static struct lconv* localeconv()
|
static struct lconv* localeconv()
|
||||||
{
|
{
|
||||||
#ifdef NO_CPP11_STRING
|
#ifdef NO_CPP11_STRING
|
||||||
static struct lconv result;
|
static struct lconv result;
|
||||||
result.decimal_point = nullptr;
|
result.decimal_point = nullptr;
|
||||||
result.thousands_sep = nullptr;
|
result.thousands_sep = nullptr;
|
||||||
return &result;
|
return &result;
|
||||||
#else
|
#else
|
||||||
return std::localeconv();
|
return std::localeconv();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
static std::string to_string(const T& t)
|
static std::string to_string(const T& t)
|
||||||
{
|
{
|
||||||
#ifdef NO_CPP11_STRING
|
#ifdef NO_CPP11_STRING
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << t;
|
ss << t;
|
||||||
return ss.str();
|
return ss.str();
|
||||||
#else
|
#else
|
||||||
return std::to_string(t);
|
return std::to_string(t);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static long stoul(const std::string& str)
|
static long stoul(const std::string& str)
|
||||||
{
|
{
|
||||||
#ifdef NO_CPP11_STRING
|
#ifdef NO_CPP11_STRING
|
||||||
errno = 0;
|
errno = 0;
|
||||||
char *temp;
|
char* temp;
|
||||||
long val = strtol(str.c_str(), &temp, 10);
|
long val = strtol(str.c_str(), &temp, 10);
|
||||||
if (temp == str.c_str() || *temp != '\0')
|
if (temp == str.c_str() || *temp != '\0')
|
||||||
throw std::invalid_argument("stoi");
|
throw std::invalid_argument("stoi");
|
||||||
if (((val == LONG_MIN) || (val == LONG_MAX)) && (errno == ERANGE))
|
if (((val == LONG_MIN) || (val == LONG_MAX)) && (errno == ERANGE))
|
||||||
throw std::out_of_range("stoi");
|
throw std::out_of_range("stoi");
|
||||||
return val;
|
return val;
|
||||||
#else
|
#else
|
||||||
return std::stoul(str);
|
return std::stoul(str);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stoi(const std::string& str)
|
static int stoi(const std::string& str)
|
||||||
{
|
{
|
||||||
#ifdef NO_CPP11_STRING
|
#ifdef NO_CPP11_STRING
|
||||||
return cpt::stoul(str);
|
return cpt::stoul(str);
|
||||||
#else
|
#else
|
||||||
return std::stoi(str);
|
return std::stoi(str);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static double stod(const std::string& str)
|
static double stod(const std::string& str)
|
||||||
{
|
{
|
||||||
#ifdef NO_CPP11_STRING
|
#ifdef NO_CPP11_STRING
|
||||||
errno = 0;
|
errno = 0;
|
||||||
char *temp;
|
char* temp;
|
||||||
double val = strtod(str.c_str(), &temp);
|
double val = strtod(str.c_str(), &temp);
|
||||||
if (temp == str.c_str() || *temp != '\0')
|
if (temp == str.c_str() || *temp != '\0')
|
||||||
throw std::invalid_argument("stod");
|
throw std::invalid_argument("stod");
|
||||||
if ((val == HUGE_VAL) && (errno == ERANGE))
|
if ((val == HUGE_VAL) && (errno == ERANGE))
|
||||||
throw std::out_of_range("stod");
|
throw std::out_of_range("stod");
|
||||||
return val;
|
return val;
|
||||||
#else
|
#else
|
||||||
return std::stod(str.c_str());
|
return std::stod(str.c_str());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static long double strtold(const char* str, char** endptr)
|
static long double strtold(const char* str, char** endptr)
|
||||||
{
|
{
|
||||||
#ifdef NO_CPP11_STRING
|
#ifdef NO_CPP11_STRING
|
||||||
return cpt::stod(str);
|
return cpt::stod(str);
|
||||||
#else
|
#else
|
||||||
return std::strtold(str, endptr);
|
return std::strtold(str, endptr);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static float strtof(const char* str, char** endptr)
|
static float strtof(const char* str, char** endptr)
|
||||||
{
|
{
|
||||||
#ifdef NO_CPP11_STRING
|
#ifdef NO_CPP11_STRING
|
||||||
return (float)cpt::stod(str);
|
return (float)cpt::stod(str);
|
||||||
#else
|
#else
|
||||||
return std::strtof(str, endptr);
|
return std::strtof(str, endptr);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,8 +20,8 @@
|
||||||
#define TIME_DEFS_H
|
#define TIME_DEFS_H
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
#include <thread>
|
||||||
#ifdef MACOS
|
#ifdef MACOS
|
||||||
#include <mach/clock.h>
|
#include <mach/clock.h>
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
|
@ -29,30 +29,30 @@
|
||||||
|
|
||||||
namespace chronos
|
namespace chronos
|
||||||
{
|
{
|
||||||
typedef std::chrono::system_clock clk;
|
typedef std::chrono::system_clock clk;
|
||||||
typedef std::chrono::time_point<clk> time_point_clk;
|
typedef std::chrono::time_point<clk> time_point_clk;
|
||||||
typedef std::chrono::seconds sec;
|
typedef std::chrono::seconds sec;
|
||||||
typedef std::chrono::milliseconds msec;
|
typedef std::chrono::milliseconds msec;
|
||||||
typedef std::chrono::microseconds usec;
|
typedef std::chrono::microseconds usec;
|
||||||
typedef std::chrono::nanoseconds nsec;
|
typedef std::chrono::nanoseconds nsec;
|
||||||
|
|
||||||
template <class Clock>
|
template <class Clock>
|
||||||
inline static void timeofday(struct timeval *tv)
|
inline static void timeofday(struct timeval* tv)
|
||||||
{
|
{
|
||||||
auto now = Clock::now();
|
auto now = Clock::now();
|
||||||
auto millisecs = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
|
auto millisecs = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
|
||||||
tv->tv_sec = millisecs.count()/1000;
|
tv->tv_sec = millisecs.count() / 1000;
|
||||||
tv->tv_usec = (millisecs.count()%1000)*1000;
|
tv->tv_usec = (millisecs.count() % 1000) * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static void systemtimeofday(struct timeval *tv)
|
inline static void systemtimeofday(struct timeval* tv)
|
||||||
{
|
{
|
||||||
gettimeofday(tv, NULL);
|
gettimeofday(tv, NULL);
|
||||||
//timeofday<std::chrono::system_clock>(tv);
|
// timeofday<std::chrono::system_clock>(tv);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static void addUs(timeval& tv, int us)
|
inline static void addUs(timeval& tv, int us)
|
||||||
{
|
{
|
||||||
if (us < 0)
|
if (us < 0)
|
||||||
{
|
{
|
||||||
timeval t;
|
timeval t;
|
||||||
|
@ -64,60 +64,58 @@ namespace chronos
|
||||||
tv.tv_usec += us;
|
tv.tv_usec += us;
|
||||||
tv.tv_sec += (tv.tv_usec / 1000000);
|
tv.tv_sec += (tv.tv_usec / 1000000);
|
||||||
tv.tv_usec %= 1000000;
|
tv.tv_usec %= 1000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static long getTickCount()
|
inline static long getTickCount()
|
||||||
{
|
{
|
||||||
#ifdef MACOS
|
#ifdef MACOS
|
||||||
clock_serv_t cclock;
|
clock_serv_t cclock;
|
||||||
mach_timespec_t mts;
|
mach_timespec_t mts;
|
||||||
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
|
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
|
||||||
clock_get_time(cclock, &mts);
|
clock_get_time(cclock, &mts);
|
||||||
mach_port_deallocate(mach_task_self(), cclock);
|
mach_port_deallocate(mach_task_self(), cclock);
|
||||||
return mts.tv_sec*1000 + mts.tv_nsec / 1000000;
|
return mts.tv_sec * 1000 + mts.tv_nsec / 1000000;
|
||||||
#else
|
#else
|
||||||
struct timespec now;
|
struct timespec now;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
return now.tv_sec*1000 + now.tv_nsec / 1000000;
|
return now.tv_sec * 1000 + now.tv_nsec / 1000000;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Rep, class Period>
|
template <class Rep, class Period>
|
||||||
inline std::chrono::duration<Rep, Period> abs(std::chrono::duration<Rep, Period> d)
|
inline std::chrono::duration<Rep, Period> abs(std::chrono::duration<Rep, Period> d)
|
||||||
{
|
{
|
||||||
Rep x = d.count();
|
Rep x = d.count();
|
||||||
return std::chrono::duration<Rep, Period>(x >= 0 ? x : -x);
|
return std::chrono::duration<Rep, Period>(x >= 0 ? x : -x);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class ToDuration, class Rep, class Period>
|
template <class ToDuration, class Rep, class Period>
|
||||||
inline int64_t duration(std::chrono::duration<Rep, Period> d)
|
inline int64_t duration(std::chrono::duration<Rep, Period> d)
|
||||||
{
|
{
|
||||||
return std::chrono::duration_cast<ToDuration>(d).count();
|
return std::chrono::duration_cast<ToDuration>(d).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// some sleep functions. Just for convenience.
|
/// some sleep functions. Just for convenience.
|
||||||
template< class Rep, class Period >
|
template <class Rep, class Period>
|
||||||
inline void sleep(const std::chrono::duration<Rep, Period>& sleep_duration)
|
inline void sleep(const std::chrono::duration<Rep, Period>& sleep_duration)
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(sleep_duration);
|
std::this_thread::sleep_for(sleep_duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void sleep(const int32_t& milliseconds)
|
inline void sleep(const int32_t& milliseconds)
|
||||||
{
|
{
|
||||||
if (milliseconds < 0)
|
if (milliseconds < 0)
|
||||||
return;
|
return;
|
||||||
sleep(msec(milliseconds));
|
sleep(msec(milliseconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void usleep(const int32_t& microseconds)
|
inline void usleep(const int32_t& microseconds)
|
||||||
{
|
{
|
||||||
if (microseconds < 0)
|
if (microseconds < 0)
|
||||||
return;
|
return;
|
||||||
sleep(usec(microseconds));
|
sleep(usec(microseconds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -22,34 +22,34 @@
|
||||||
#include "common/strCompat.h"
|
#include "common/strCompat.h"
|
||||||
#include "common/utils/string_utils.h"
|
#include "common/utils/string_utils.h"
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <locale>
|
|
||||||
#include <string>
|
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <memory>
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iomanip>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <sys/ioctl.h>
|
#include <locale>
|
||||||
|
#include <memory>
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <sys/types.h>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <iomanip>
|
#include <vector>
|
||||||
#ifndef FREEBSD
|
#ifndef FREEBSD
|
||||||
#include <sys/sysinfo.h>
|
#include <sys/sysinfo.h>
|
||||||
#endif
|
#endif
|
||||||
#include <sys/utsname.h>
|
#include <sys/utsname.h>
|
||||||
#ifdef MACOS
|
#ifdef MACOS
|
||||||
#include <ifaddrs.h>
|
|
||||||
#include <net/if_dl.h>
|
|
||||||
#include <IOKit/IOCFPlugIn.h>
|
#include <IOKit/IOCFPlugIn.h>
|
||||||
#include <IOKit/IOTypes.h>
|
#include <IOKit/IOTypes.h>
|
||||||
|
#include <ifaddrs.h>
|
||||||
|
#include <net/if_dl.h>
|
||||||
#endif
|
#endif
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
#include <sys/system_properties.h>
|
#include <sys/system_properties.h>
|
||||||
|
@ -80,7 +80,7 @@ static std::string execGetOutput(const std::string& cmd)
|
||||||
static std::string getProp(const std::string& key, const std::string& def = "")
|
static std::string getProp(const std::string& key, const std::string& def = "")
|
||||||
{
|
{
|
||||||
std::string result(def);
|
std::string result(def);
|
||||||
char cresult[PROP_VALUE_MAX+1];
|
char cresult[PROP_VALUE_MAX + 1];
|
||||||
if (__system_property_get(key.c_str(), cresult) > 0)
|
if (__system_property_get(key.c_str(), cresult) > 0)
|
||||||
result = cresult;
|
result = cresult;
|
||||||
return result;
|
return result;
|
||||||
|
@ -186,12 +186,9 @@ static std::string generateUUID()
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << std::setfill('0') << std::hex
|
ss << std::setfill('0') << std::hex << std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff) << "-" << std::setw(4)
|
||||||
<< std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff)
|
<< (std::rand() % 0xffff) << "-" << std::setw(4) << (std::rand() % 0xffff) << "-" << std::setw(4) << (std::rand() % 0xffff) << "-" << std::setw(4)
|
||||||
<< "-" << std::setw(4) << (std::rand() % 0xffff)
|
<< (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff);
|
||||||
<< "-" << std::setw(4) << (std::rand() % 0xffff)
|
|
||||||
<< "-" << std::setw(4) << (std::rand() % 0xffff)
|
|
||||||
<< "-" << std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff) << std::setw(4) << (std::rand() % 0xffff);
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,9 +210,9 @@ static std::string getMacAddress(int sock)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
struct ifreq* it = ifc.ifc_req;
|
struct ifreq* it = ifc.ifc_req;
|
||||||
for (int i=0; i<ifc.ifc_len;)
|
for (int i = 0; i < ifc.ifc_len;)
|
||||||
{
|
{
|
||||||
/// some systems have ifr_addr.sa_len and adjust the length that way, but not mine. weird */
|
/// some systems have ifr_addr.sa_len and adjust the length that way, but not mine. weird */
|
||||||
#ifdef FREEBSD
|
#ifdef FREEBSD
|
||||||
size_t len = IFNAMSIZ + it->ifr_addr.sa_len;
|
size_t len = IFNAMSIZ + it->ifr_addr.sa_len;
|
||||||
#else
|
#else
|
||||||
|
@ -230,20 +227,20 @@ static std::string getMacAddress(int sock)
|
||||||
#ifdef MACOS
|
#ifdef MACOS
|
||||||
/// Dirty Mac version
|
/// Dirty Mac version
|
||||||
struct ifaddrs *ifap, *ifaptr;
|
struct ifaddrs *ifap, *ifaptr;
|
||||||
unsigned char *ptr;
|
unsigned char* ptr;
|
||||||
|
|
||||||
if (getifaddrs(&ifap) == 0)
|
if (getifaddrs(&ifap) == 0)
|
||||||
{
|
{
|
||||||
for (ifaptr = ifap; ifaptr != NULL; ifaptr = ifaptr->ifa_next)
|
for (ifaptr = ifap; ifaptr != NULL; ifaptr = ifaptr->ifa_next)
|
||||||
{
|
{
|
||||||
// std::cout << ifaptr->ifa_name << ", " << ifreq->ifr_name << "\n";
|
// std::cout << ifaptr->ifa_name << ", " << ifreq->ifr_name << "\n";
|
||||||
if (strcmp(ifaptr->ifa_name, it->ifr_name) != 0)
|
if (strcmp(ifaptr->ifa_name, it->ifr_name) != 0)
|
||||||
continue;
|
continue;
|
||||||
if (ifaptr->ifa_addr->sa_family == AF_LINK)
|
if (ifaptr->ifa_addr->sa_family == AF_LINK)
|
||||||
{
|
{
|
||||||
ptr = (unsigned char *)LLADDR((struct sockaddr_dl *)ifaptr->ifa_addr);
|
ptr = (unsigned char*)LLADDR((struct sockaddr_dl*)ifaptr->ifa_addr);
|
||||||
char mac[19];
|
char mac[19];
|
||||||
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5));
|
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4), *(ptr + 5));
|
||||||
if (strcmp(mac, "00:00:00:00:00:00") == 0)
|
if (strcmp(mac, "00:00:00:00:00:00") == 0)
|
||||||
continue;
|
continue;
|
||||||
freeifaddrs(ifap);
|
freeifaddrs(ifap);
|
||||||
|
@ -278,7 +275,9 @@ static std::string getMacAddress(int sock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { /* handle error */ }
|
else
|
||||||
|
{ /* handle error */
|
||||||
|
}
|
||||||
|
|
||||||
it = (struct ifreq*)((char*)it + len);
|
it = (struct ifreq*)((char*)it + len);
|
||||||
i += len;
|
i += len;
|
||||||
|
@ -289,13 +288,13 @@ static std::string getMacAddress(int sock)
|
||||||
|
|
||||||
char mac[19];
|
char mac[19];
|
||||||
#ifndef FREEBSD
|
#ifndef FREEBSD
|
||||||
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x",
|
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", (unsigned char)ifr.ifr_hwaddr.sa_data[0], (unsigned char)ifr.ifr_hwaddr.sa_data[1],
|
||||||
(unsigned char)ifr.ifr_hwaddr.sa_data[0], (unsigned char)ifr.ifr_hwaddr.sa_data[1], (unsigned char)ifr.ifr_hwaddr.sa_data[2],
|
(unsigned char)ifr.ifr_hwaddr.sa_data[2], (unsigned char)ifr.ifr_hwaddr.sa_data[3], (unsigned char)ifr.ifr_hwaddr.sa_data[4],
|
||||||
(unsigned char)ifr.ifr_hwaddr.sa_data[3], (unsigned char)ifr.ifr_hwaddr.sa_data[4], (unsigned char)ifr.ifr_hwaddr.sa_data[5]);
|
(unsigned char)ifr.ifr_hwaddr.sa_data[5]);
|
||||||
#else
|
#else
|
||||||
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x",
|
sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[0], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[1],
|
||||||
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[0], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[1], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[2],
|
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[2], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[3],
|
||||||
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[3], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[4], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[5]);
|
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[4], (unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[5]);
|
||||||
#endif
|
#endif
|
||||||
return mac;
|
return mac;
|
||||||
}
|
}
|
||||||
|
@ -315,7 +314,7 @@ static std::string getHostId(const std::string defaultId = "")
|
||||||
/// About this Mac, Hardware-UUID
|
/// About this Mac, Hardware-UUID
|
||||||
char buf[64];
|
char buf[64];
|
||||||
io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
|
io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
|
||||||
CFStringRef uuidCf = (CFStringRef) IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0);
|
CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0);
|
||||||
IOObjectRelease(ioRegistryRoot);
|
IOObjectRelease(ioRegistryRoot);
|
||||||
if (CFStringGetCString(uuidCf, buf, 64, kCFStringEncodingMacRoman))
|
if (CFStringGetCString(uuidCf, buf, 64, kCFStringEncodingMacRoman))
|
||||||
result = buf;
|
result = buf;
|
||||||
|
@ -324,15 +323,15 @@ static std::string getHostId(const std::string defaultId = "")
|
||||||
result = getProp("ro.serialno");
|
result = getProp("ro.serialno");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//#else
|
//#else
|
||||||
// // on embedded platforms it's
|
// // on embedded platforms it's
|
||||||
// // - either not there
|
// // - either not there
|
||||||
// // - or not unique, or changes during boot
|
// // - or not unique, or changes during boot
|
||||||
// // - or changes during boot
|
// // - or changes during boot
|
||||||
// std::ifstream infile("/var/lib/dbus/machine-id");
|
// std::ifstream infile("/var/lib/dbus/machine-id");
|
||||||
// if (infile.good())
|
// if (infile.good())
|
||||||
// std::getline(infile, result);
|
// std::getline(infile, result);
|
||||||
//#endif
|
//#endif
|
||||||
strutils::trim(result);
|
strutils::trim(result);
|
||||||
if (!result.empty())
|
if (!result.empty())
|
||||||
return result;
|
return result;
|
||||||
|
@ -343,5 +342,3 @@ static std::string getHostId(const std::string defaultId = "")
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,12 +19,12 @@
|
||||||
#ifndef FILE_UTILS_H
|
#ifndef FILE_UTILS_H
|
||||||
#define FILE_UTILS_H
|
#define FILE_UTILS_H
|
||||||
|
|
||||||
|
#include "string_utils.h"
|
||||||
|
#include <fstream>
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fstream>
|
|
||||||
#include "string_utils.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace utils
|
namespace utils
|
||||||
|
@ -53,7 +53,7 @@ static void do_chown(const std::string& file_path, const std::string& user_name,
|
||||||
|
|
||||||
if (!user_name.empty())
|
if (!user_name.empty())
|
||||||
{
|
{
|
||||||
struct passwd *pwd = getpwnam(user_name.c_str());
|
struct passwd* pwd = getpwnam(user_name.c_str());
|
||||||
if (pwd == NULL)
|
if (pwd == NULL)
|
||||||
throw std::runtime_error("Failed to get uid");
|
throw std::runtime_error("Failed to get uid");
|
||||||
uid = pwd->pw_uid;
|
uid = pwd->pw_uid;
|
||||||
|
@ -61,7 +61,7 @@ static void do_chown(const std::string& file_path, const std::string& user_name,
|
||||||
|
|
||||||
if (!group_name.empty())
|
if (!group_name.empty())
|
||||||
{
|
{
|
||||||
struct group *grp = getgrnam(group_name.c_str());
|
struct group* grp = getgrnam(group_name.c_str());
|
||||||
if (grp == NULL)
|
if (grp == NULL)
|
||||||
throw std::runtime_error("Failed to get gid");
|
throw std::runtime_error("Failed to get gid");
|
||||||
gid = grp->gr_gid;
|
gid = grp->gr_gid;
|
||||||
|
@ -72,12 +72,12 @@ static void do_chown(const std::string& file_path, const std::string& user_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int mkdirRecursive(const char *path, mode_t mode)
|
static int mkdirRecursive(const char* path, mode_t mode)
|
||||||
{
|
{
|
||||||
std::vector<std::string> pathes = utils::string::split(path, '/');
|
std::vector<std::string> pathes = utils::string::split(path, '/');
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
int res = 0;
|
int res = 0;
|
||||||
for (const auto& p: pathes)
|
for (const auto& p : pathes)
|
||||||
{
|
{
|
||||||
if (p.empty())
|
if (p.empty())
|
||||||
continue;
|
continue;
|
||||||
|
@ -93,4 +93,3 @@ static int mkdirRecursive(const char *path, mode_t mode)
|
||||||
} // namespace utils
|
} // namespace utils
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
#ifndef STRING_UTILS_H
|
#ifndef STRING_UTILS_H
|
||||||
#define STRING_UTILS_H
|
#define STRING_UTILS_H
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace utils
|
namespace utils
|
||||||
|
@ -31,56 +31,57 @@ namespace string
|
||||||
{
|
{
|
||||||
|
|
||||||
// trim from start
|
// trim from start
|
||||||
static inline std::string <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<int, int>(std::isspace))));
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace))));
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim from end
|
// trim from end
|
||||||
static inline std::string &rtrim(std::string &s)
|
static inline std::string& rtrim(std::string& s)
|
||||||
{
|
{
|
||||||
s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
|
s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim from both ends
|
// trim from both ends
|
||||||
static inline std::string &trim(std::string &s)
|
static inline std::string& trim(std::string& s)
|
||||||
{
|
{
|
||||||
return ltrim(rtrim(s));
|
return ltrim(rtrim(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim from start
|
// trim from start
|
||||||
static inline std::string ltrim_copy(const std::string &s)
|
static inline std::string ltrim_copy(const std::string& s)
|
||||||
{
|
{
|
||||||
std::string str(s);
|
std::string str(s);
|
||||||
return ltrim(str);
|
return ltrim(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim from end
|
// trim from end
|
||||||
static inline std::string rtrim_copy(const std::string &s)
|
static inline std::string rtrim_copy(const std::string& s)
|
||||||
{
|
{
|
||||||
std::string str(s);
|
std::string str(s);
|
||||||
return rtrim(str);
|
return rtrim(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim from both ends
|
// trim from both ends
|
||||||
static inline std::string trim_copy(const std::string &s)
|
static inline std::string trim_copy(const std::string& s)
|
||||||
{
|
{
|
||||||
std::string str(s);
|
std::string str(s);
|
||||||
return trim(str);
|
return trim(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode %xx to char
|
// decode %xx to char
|
||||||
static std::string uriDecode(const std::string& src) {
|
static std::string uriDecode(const std::string& src)
|
||||||
|
{
|
||||||
std::string ret;
|
std::string ret;
|
||||||
char ch;
|
char ch;
|
||||||
for (size_t i=0; i<src.length(); i++)
|
for (size_t i = 0; i < src.length(); i++)
|
||||||
{
|
{
|
||||||
if (int(src[i]) == 37)
|
if (int(src[i]) == 37)
|
||||||
{
|
{
|
||||||
unsigned int ii;
|
unsigned int ii;
|
||||||
sscanf(src.substr(i+1, 2).c_str(), "%x", &ii);
|
sscanf(src.substr(i + 1, 2).c_str(), "%x", &ii);
|
||||||
ch = static_cast<char>(ii);
|
ch = static_cast<char>(ii);
|
||||||
ret += ch;
|
ret += ch;
|
||||||
i += 2;
|
i += 2;
|
||||||
|
@ -95,7 +96,7 @@ static std::string uriDecode(const std::string& src) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems)
|
static std::vector<std::string>& split(const std::string& s, char delim, std::vector<std::string>& elems)
|
||||||
{
|
{
|
||||||
std::stringstream ss(s);
|
std::stringstream ss(s);
|
||||||
std::string item;
|
std::string item;
|
||||||
|
@ -107,7 +108,7 @@ static std::vector<std::string> &split(const std::string &s, char delim, std::ve
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static std::vector<std::string> split(const std::string &s, char delim)
|
static std::vector<std::string> split(const std::string& s, char delim)
|
||||||
{
|
{
|
||||||
std::vector<std::string> elems;
|
std::vector<std::string> elems;
|
||||||
split(s, delim, elems);
|
split(s, delim, elems);
|
||||||
|
@ -118,4 +119,3 @@ static std::vector<std::string> split(const std::string &s, char delim)
|
||||||
} // namespace utils
|
} // namespace utils
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
179
doc/snapcast_plain_icon.svg
Normal file
179
doc/snapcast_plain_icon.svg
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="220mm"
|
||||||
|
height="220mm"
|
||||||
|
viewBox="0 0 779.52756 779.52755"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="snapcast_plain_icon.svg"
|
||||||
|
inkscape:export-filename="/home/adminuser/Desktop/drawing.png"
|
||||||
|
inkscape:export-xdpi="90"
|
||||||
|
inkscape:export-ydpi="90">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffc107"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.70710678"
|
||||||
|
inkscape:cx="379.04793"
|
||||||
|
inkscape:cy="415.07164"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:snap-page="true"
|
||||||
|
inkscape:snap-grids="true"
|
||||||
|
inkscape:snap-object-midpoints="true"
|
||||||
|
inkscape:snap-center="true"
|
||||||
|
inkscape:window-width="1440"
|
||||||
|
inkscape:window-height="847"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
borderlayer="false"
|
||||||
|
inkscape:showpageshadow="true" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-272.83465)">
|
||||||
|
<path
|
||||||
|
style="fill:#c8c8c8;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:33.33300018;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 500.92383 225.22852 L 386.56445 324.14062 L 278.33008 324.14062 L 278.33008 451.42773 L 384.92383 451.42773 L 500.92383 551.76758 L 500.92383 225.22852 z "
|
||||||
|
transform="translate(0,272.83462)"
|
||||||
|
id="polygon1-3" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
|
||||||
|
id="path3353"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="389.62741"
|
||||||
|
sodipodi:cy="663.36218"
|
||||||
|
sodipodi:rx="360.297"
|
||||||
|
sodipodi:ry="360.297"
|
||||||
|
sodipodi:start="5.7595865"
|
||||||
|
sodipodi:end="0.52359878"
|
||||||
|
d="m 701.65376,483.21367 a 360.297,360.297 0 0 1 0,360.29701"
|
||||||
|
sodipodi:open="true" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
|
||||||
|
id="path3353-6"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="389.62741"
|
||||||
|
sodipodi:cy="663.36218"
|
||||||
|
sodipodi:rx="299.98151"
|
||||||
|
sodipodi:ry="299.98151"
|
||||||
|
sodipodi:start="5.8119464"
|
||||||
|
sodipodi:end="0.4712389"
|
||||||
|
d="m 656.91289,527.17343 a 299.98151,299.98151 0 0 1 0,272.37751"
|
||||||
|
sodipodi:open="true" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
|
||||||
|
id="path3353-6-5"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="389.62741"
|
||||||
|
sodipodi:cy="663.36218"
|
||||||
|
sodipodi:rx="240.16534"
|
||||||
|
sodipodi:ry="240.16534"
|
||||||
|
sodipodi:start="5.8643063"
|
||||||
|
sodipodi:end="0.41887902"
|
||||||
|
d="m 609.02937,565.67814 a 240.16534,240.16534 0 0 1 0,195.36809"
|
||||||
|
sodipodi:open="true" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
|
||||||
|
id="path3353-1"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="389.62741"
|
||||||
|
sodipodi:cy="663.36218"
|
||||||
|
sodipodi:rx="360.297"
|
||||||
|
sodipodi:ry="360.297"
|
||||||
|
sodipodi:start="3.6651914"
|
||||||
|
sodipodi:end="4.712389"
|
||||||
|
d="M 77.601053,483.21369 A 360.297,360.297 0 0 1 389.62742,303.06519"
|
||||||
|
sodipodi:open="true" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
|
||||||
|
id="path3353-6-2"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="389.62741"
|
||||||
|
sodipodi:cy="663.36218"
|
||||||
|
sodipodi:rx="299.98151"
|
||||||
|
sodipodi:ry="299.98151"
|
||||||
|
sodipodi:start="3.7175513"
|
||||||
|
sodipodi:end="4.6600291"
|
||||||
|
d="M 138.04175,499.98055 A 299.98151,299.98151 0 0 1 373.92759,363.79179"
|
||||||
|
sodipodi:open="true" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
|
||||||
|
id="path3353-6-5-7"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="389.62741"
|
||||||
|
sodipodi:cy="663.36218"
|
||||||
|
sodipodi:rx="240.16534"
|
||||||
|
sodipodi:ry="240.16534"
|
||||||
|
sodipodi:start="3.7699112"
|
||||||
|
sodipodi:end="4.6076692"
|
||||||
|
d="M 195.32957,522.19653 A 240.16534,240.16534 0 0 1 364.52329,424.51249"
|
||||||
|
sodipodi:open="true" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
|
||||||
|
id="path3353-1-6"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="389.62741"
|
||||||
|
sodipodi:cy="663.36218"
|
||||||
|
sodipodi:rx="360.297"
|
||||||
|
sodipodi:ry="360.297"
|
||||||
|
sodipodi:start="1.5707963"
|
||||||
|
sodipodi:end="2.6179939"
|
||||||
|
d="M 389.62742,1023.6592 A 360.297,360.297 0 0 1 77.601055,843.51067"
|
||||||
|
sodipodi:open="true" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
|
||||||
|
id="path3353-6-2-0"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="389.62741"
|
||||||
|
sodipodi:cy="663.36218"
|
||||||
|
sodipodi:rx="299.98151"
|
||||||
|
sodipodi:ry="299.98151"
|
||||||
|
sodipodi:start="1.6231562"
|
||||||
|
sodipodi:end="2.565634"
|
||||||
|
d="M 373.92759,962.93257 A 299.98151,299.98151 0 0 1 138.04175,826.74382"
|
||||||
|
sodipodi:open="true" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:35;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0"
|
||||||
|
id="path3353-6-5-7-6"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="389.62741"
|
||||||
|
sodipodi:cy="663.36218"
|
||||||
|
sodipodi:rx="240.16534"
|
||||||
|
sodipodi:ry="240.16534"
|
||||||
|
sodipodi:start="1.6755161"
|
||||||
|
sodipodi:end="2.5132741"
|
||||||
|
d="M 364.52329,902.21188 A 240.16534,240.16534 0 0 1 195.32957,804.52783"
|
||||||
|
sodipodi:open="true" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
2
externals/Makefile
vendored
2
externals/Makefile
vendored
|
@ -1,5 +1,5 @@
|
||||||
# This file is part of snapcast
|
# This file is part of snapcast
|
||||||
# Copyright (C) 2014-2018 Johannes Pohl
|
# Copyright (C) 2014-2019 Johannes Pohl
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# This file is part of snapcast
|
# This file is part of snapcast
|
||||||
# Copyright (C) 2014-2018 Johannes Pohl
|
# Copyright (C) 2014-2019 Johannes Pohl
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,15 +17,15 @@
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <sys/types.h>
|
#include "aixlog.hpp"
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <cerrno>
|
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
#include "common/strCompat.h"
|
#include "common/strCompat.h"
|
||||||
#include "common/utils/file_utils.h"
|
#include "common/utils/file_utils.h"
|
||||||
#include "aixlog.hpp"
|
#include <cerrno>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ void Config::init(const std::string& root_directory, const std::string& user, co
|
||||||
utils::file::do_chown(dir, user, group);
|
utils::file::do_chown(dir, user, group);
|
||||||
utils::file::do_chown(filename_, user, group);
|
utils::file::do_chown(filename_, user, group);
|
||||||
}
|
}
|
||||||
catch(const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
SLOG(ERROR) << "Exception in chown: " << e.what() << "\n";
|
SLOG(ERROR) << "Exception in chown: " << e.what() << "\n";
|
||||||
}
|
}
|
||||||
|
@ -101,14 +101,14 @@ void Config::init(const std::string& root_directory, const std::string& user, co
|
||||||
{
|
{
|
||||||
GroupPtr group = make_shared<Group>();
|
GroupPtr group = make_shared<Group>();
|
||||||
group->fromJson(*it);
|
group->fromJson(*it);
|
||||||
// if (client->id.empty() || getClientInfo(client->id))
|
// if (client->id.empty() || getClientInfo(client->id))
|
||||||
// continue;
|
// continue;
|
||||||
groups.push_back(group);
|
groups.push_back(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
LOG(ERROR) << "Error reading config: " << e.what() << "\n";
|
LOG(ERROR) << "Error reading config: " << e.what() << "\n";
|
||||||
}
|
}
|
||||||
|
@ -119,11 +119,8 @@ void Config::save()
|
||||||
{
|
{
|
||||||
if (filename_.empty())
|
if (filename_.empty())
|
||||||
init();
|
init();
|
||||||
std::ofstream ofs(filename_.c_str(), std::ofstream::out|std::ofstream::trunc);
|
std::ofstream ofs(filename_.c_str(), std::ofstream::out | std::ofstream::trunc);
|
||||||
json clients = {
|
json clients = {{"ConfigVersion", 2}, {"Groups", getGroups()}};
|
||||||
{"ConfigVersion", 2},
|
|
||||||
{"Groups", getGroups()}
|
|
||||||
};
|
|
||||||
ofs << std::setw(4) << clients;
|
ofs << std::setw(4) << clients;
|
||||||
ofs.close();
|
ofs.close();
|
||||||
}
|
}
|
||||||
|
@ -134,9 +131,9 @@ ClientInfoPtr Config::getClientInfo(const std::string& clientId) const
|
||||||
if (clientId.empty())
|
if (clientId.empty())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
for (auto group: groups)
|
for (auto group : groups)
|
||||||
{
|
{
|
||||||
for (auto client: group->clients)
|
for (auto client : group->clients)
|
||||||
{
|
{
|
||||||
if (client->id == clientId)
|
if (client->id == clientId)
|
||||||
return client;
|
return client;
|
||||||
|
@ -171,7 +168,7 @@ GroupPtr Config::addClientInfo(const std::string& clientId)
|
||||||
|
|
||||||
GroupPtr Config::getGroup(const std::string& groupId) const
|
GroupPtr Config::getGroup(const std::string& groupId) const
|
||||||
{
|
{
|
||||||
for (auto group: groups)
|
for (auto group : groups)
|
||||||
{
|
{
|
||||||
if (group->id == groupId)
|
if (group->id == groupId)
|
||||||
return group;
|
return group;
|
||||||
|
@ -183,9 +180,9 @@ GroupPtr Config::getGroup(const std::string& groupId) const
|
||||||
|
|
||||||
GroupPtr Config::getGroupFromClient(const std::string& clientId)
|
GroupPtr Config::getGroupFromClient(const std::string& clientId)
|
||||||
{
|
{
|
||||||
for (auto group: groups)
|
for (auto group : groups)
|
||||||
{
|
{
|
||||||
for (auto c: group->clients)
|
for (auto c : group->clients)
|
||||||
{
|
{
|
||||||
if (c->id == clientId)
|
if (c->id == clientId)
|
||||||
return group;
|
return group;
|
||||||
|
@ -205,16 +202,13 @@ json Config::getServerStatus(const json& streams) const
|
||||||
{
|
{
|
||||||
Host host;
|
Host host;
|
||||||
host.update();
|
host.update();
|
||||||
//TODO: Set MAC and IP
|
// TODO: Set MAC and IP
|
||||||
Snapserver snapserver("Snapserver", VERSION);
|
Snapserver snapserver("Snapserver", VERSION);
|
||||||
json serverStatus = {
|
json serverStatus = {{"server",
|
||||||
{"server", {
|
{{"host", host.toJson()}, // getHostName()},
|
||||||
{"host", host.toJson()},//getHostName()},
|
{"snapserver", snapserver.toJson()}}},
|
||||||
{"snapserver", snapserver.toJson()}
|
|
||||||
}},
|
|
||||||
{"groups", getGroups()},
|
{"groups", getGroups()},
|
||||||
{"streams", streams}
|
{"streams", streams}};
|
||||||
};
|
|
||||||
|
|
||||||
return serverStatus;
|
return serverStatus;
|
||||||
}
|
}
|
||||||
|
@ -224,7 +218,7 @@ json Config::getServerStatus(const json& streams) const
|
||||||
json Config::getGroups() const
|
json Config::getGroups() const
|
||||||
{
|
{
|
||||||
json result = json::array();
|
json result = json::array();
|
||||||
for (auto group: groups)
|
for (auto group : groups)
|
||||||
result.push_back(group->toJson());
|
result.push_back(group->toJson());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -287,5 +281,3 @@ GroupPtr Config::setGroupForClient(const std::string& groupId, const std::string
|
||||||
return newGroup;
|
return newGroup;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,14 +19,14 @@
|
||||||
#ifndef CONFIG_H
|
#ifndef CONFIG_H
|
||||||
#define CONFIG_H
|
#define CONFIG_H
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <string>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "common/json.hpp"
|
#include "common/json.hpp"
|
||||||
#include "common/utils/string_utils.h"
|
|
||||||
#include "common/utils.h"
|
#include "common/utils.h"
|
||||||
|
#include "common/utils/string_utils.h"
|
||||||
|
|
||||||
|
|
||||||
namespace strutils = utils::string;
|
namespace strutils = utils::string;
|
||||||
|
@ -39,7 +39,7 @@ typedef std::shared_ptr<ClientInfo> ClientInfoPtr;
|
||||||
typedef std::shared_ptr<Group> GroupPtr;
|
typedef std::shared_ptr<Group> GroupPtr;
|
||||||
|
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
T jGet(const json& j, const std::string& what, const T& def)
|
T jGet(const json& j, const std::string& what, const T& def)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -48,7 +48,7 @@ T jGet(const json& j, const std::string& what, const T& def)
|
||||||
return def;
|
return def;
|
||||||
return j[what].get<T>();
|
return j[what].get<T>();
|
||||||
}
|
}
|
||||||
catch(...)
|
catch (...)
|
||||||
{
|
{
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
@ -295,7 +295,7 @@ struct Group
|
||||||
j["muted"] = muted;
|
j["muted"] = muted;
|
||||||
|
|
||||||
json jClients = json::array();
|
json jClients = json::array();
|
||||||
for (auto client: clients)
|
for (auto client : clients)
|
||||||
jClients.push_back(client->toJson());
|
jClients.push_back(client->toJson());
|
||||||
j["clients"] = jClients;
|
j["clients"] = jClients;
|
||||||
return j;
|
return j;
|
||||||
|
@ -324,7 +324,7 @@ struct Group
|
||||||
|
|
||||||
ClientInfoPtr getClient(const std::string& clientId)
|
ClientInfoPtr getClient(const std::string& clientId)
|
||||||
{
|
{
|
||||||
for (auto client: clients)
|
for (auto client : clients)
|
||||||
{
|
{
|
||||||
if (client->id == clientId)
|
if (client->id == clientId)
|
||||||
return client;
|
return client;
|
||||||
|
@ -337,19 +337,19 @@ struct Group
|
||||||
if (!client)
|
if (!client)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (auto c: clients)
|
for (auto c : clients)
|
||||||
{
|
{
|
||||||
if (c->id == client->id)
|
if (c->id == client->id)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clients.push_back(client);
|
clients.push_back(client);
|
||||||
/* sort(clients.begin(), clients.end(),
|
/* sort(clients.begin(), clients.end(),
|
||||||
[](const ClientInfoPtr a, const ClientInfoPtr b) -> bool
|
[](const ClientInfoPtr a, const ClientInfoPtr b) -> bool
|
||||||
{
|
{
|
||||||
return a.name > b.name;
|
return a.name > b.name;
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
bool empty() const
|
bool empty() const
|
||||||
|
@ -380,8 +380,8 @@ public:
|
||||||
void remove(ClientInfoPtr client);
|
void remove(ClientInfoPtr client);
|
||||||
void remove(GroupPtr group, bool force = false);
|
void remove(GroupPtr group, bool force = false);
|
||||||
|
|
||||||
// GroupPtr removeFromGroup(const std::string& groupId, const std::string& clientId);
|
// GroupPtr removeFromGroup(const std::string& groupId, const std::string& clientId);
|
||||||
// GroupPtr setGroupForClient(const std::string& groupId, const std::string& clientId);
|
// GroupPtr setGroupForClient(const std::string& groupId, const std::string& clientId);
|
||||||
|
|
||||||
GroupPtr getGroupFromClient(const std::string& clientId);
|
GroupPtr getGroupFromClient(const std::string& clientId);
|
||||||
GroupPtr getGroupFromClient(ClientInfoPtr client);
|
GroupPtr getGroupFromClient(ClientInfoPtr client);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,13 +16,13 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include "jsonrpcpp.hpp"
|
|
||||||
#include "controlServer.h"
|
#include "controlServer.h"
|
||||||
#include "message/time.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
#include "common/utils.h"
|
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
|
#include "common/utils.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "jsonrpcpp.hpp"
|
||||||
|
#include "message/time.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -30,32 +30,28 @@ using namespace std;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
|
||||||
ControlServer::ControlServer(asio::io_service* io_service, size_t port, ControlMessageReceiver* controlMessageReceiver) :
|
ControlServer::ControlServer(asio::io_service* io_service, size_t port, ControlMessageReceiver* controlMessageReceiver)
|
||||||
acceptor_v4_(nullptr),
|
: acceptor_v4_(nullptr), acceptor_v6_(nullptr), io_service_(io_service), port_(port), controlMessageReceiver_(controlMessageReceiver)
|
||||||
acceptor_v6_(nullptr),
|
|
||||||
io_service_(io_service),
|
|
||||||
port_(port),
|
|
||||||
controlMessageReceiver_(controlMessageReceiver)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ControlServer::~ControlServer()
|
ControlServer::~ControlServer()
|
||||||
{
|
{
|
||||||
// stop();
|
// stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ControlServer::cleanup()
|
void ControlServer::cleanup()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> mlock(mutex_);
|
std::lock_guard<std::recursive_mutex> mlock(mutex_);
|
||||||
for (auto it = sessions_.begin(); it != sessions_.end(); )
|
for (auto it = sessions_.begin(); it != sessions_.end();)
|
||||||
{
|
{
|
||||||
if (!(*it)->active())
|
if (!(*it)->active())
|
||||||
{
|
{
|
||||||
SLOG(ERROR) << "Session inactive. Removing\n";
|
SLOG(ERROR) << "Session inactive. Removing\n";
|
||||||
// don't block: remove ClientSession in a thread
|
// don't block: remove ClientSession in a thread
|
||||||
auto func = [](shared_ptr<ControlSession> s)->void{s->stop();};
|
auto func = [](shared_ptr<ControlSession> s) -> void { s->stop(); };
|
||||||
std::thread t(func, *it);
|
std::thread t(func, *it);
|
||||||
t.detach();
|
t.detach();
|
||||||
//(*it)->stop();
|
//(*it)->stop();
|
||||||
|
@ -89,7 +85,7 @@ void ControlServer::onMessageReceived(ControlSession* connection, const std::str
|
||||||
if (it->get() == connection)
|
if (it->get() == connection)
|
||||||
{
|
{
|
||||||
/// delete in a thread to avoid deadlock
|
/// delete in a thread to avoid deadlock
|
||||||
auto func = [&](std::shared_ptr<ControlSession> s)->void{sessions_.erase(s);};
|
auto func = [&](std::shared_ptr<ControlSession> s) -> void { sessions_.erase(s); };
|
||||||
std::thread t(func, *it);
|
std::thread t(func, *it);
|
||||||
t.detach();
|
t.detach();
|
||||||
break;
|
break;
|
||||||
|
@ -196,7 +192,6 @@ void ControlServer::stop()
|
||||||
acceptor_v6_ = nullptr;
|
acceptor_v6_ = nullptr;
|
||||||
}
|
}
|
||||||
std::lock_guard<std::recursive_mutex> mlock(mutex_);
|
std::lock_guard<std::recursive_mutex> mlock(mutex_);
|
||||||
for (auto s: sessions_)
|
for (auto s : sessions_)
|
||||||
s->stop();
|
s->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,18 +20,18 @@
|
||||||
#define CONTROL_SERVER_H
|
#define CONTROL_SERVER_H
|
||||||
|
|
||||||
#include <asio.hpp>
|
#include <asio.hpp>
|
||||||
#include <vector>
|
|
||||||
#include <thread>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <mutex>
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "controlSession.h"
|
|
||||||
#include "common/queue.h"
|
#include "common/queue.h"
|
||||||
#include "common/sampleFormat.h"
|
#include "common/sampleFormat.h"
|
||||||
#include "message/message.h"
|
#include "controlSession.h"
|
||||||
#include "message/codecHeader.h"
|
#include "message/codecHeader.h"
|
||||||
|
#include "message/message.h"
|
||||||
#include "message/serverSettings.h"
|
#include "message/serverSettings.h"
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ private:
|
||||||
void startAccept();
|
void startAccept();
|
||||||
void handleAccept(socket_ptr socket);
|
void handleAccept(socket_ptr socket);
|
||||||
void cleanup();
|
void cleanup();
|
||||||
// void acceptor();
|
// void acceptor();
|
||||||
mutable std::recursive_mutex mutex_;
|
mutable std::recursive_mutex mutex_;
|
||||||
std::set<std::shared_ptr<ControlSession>> sessions_;
|
std::set<std::shared_ptr<ControlSession>> sessions_;
|
||||||
std::shared_ptr<tcp::acceptor> acceptor_v4_;
|
std::shared_ptr<tcp::acceptor> acceptor_v4_;
|
||||||
|
@ -77,5 +77,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,18 +16,17 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <mutex>
|
|
||||||
#include "controlSession.h"
|
#include "controlSession.h"
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
#include "message/pcmChunk.h"
|
#include "message/pcmChunk.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ControlSession::ControlSession(ControlMessageReceiver* receiver, std::shared_ptr<tcp::socket> socket) :
|
ControlSession::ControlSession(ControlMessageReceiver* receiver, std::shared_ptr<tcp::socket> socket) : active_(false), messageReceiver_(receiver)
|
||||||
active_(false), messageReceiver_(receiver)
|
|
||||||
{
|
{
|
||||||
socket_ = socket;
|
socket_ = socket;
|
||||||
}
|
}
|
||||||
|
@ -62,9 +61,11 @@ void ControlSession::stop()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> socketLock(socketMutex_);
|
std::lock_guard<std::recursive_mutex> socketLock(socketMutex_);
|
||||||
socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
||||||
if (ec) LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n";
|
if (ec)
|
||||||
|
LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n";
|
||||||
socket_->close(ec);
|
socket_->close(ec);
|
||||||
if (ec) LOG(ERROR) << "Error in socket close: " << ec.message() << "\n";
|
if (ec)
|
||||||
|
LOG(ERROR) << "Error in socket close: " << ec.message() << "\n";
|
||||||
}
|
}
|
||||||
if (readerThread_.joinable())
|
if (readerThread_.joinable())
|
||||||
{
|
{
|
||||||
|
@ -78,7 +79,7 @@ void ControlSession::stop()
|
||||||
writerThread_.join();
|
writerThread_.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(...)
|
catch (...)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
socket_ = NULL;
|
socket_ = NULL;
|
||||||
|
@ -95,7 +96,7 @@ void ControlSession::sendAsync(const std::string& message)
|
||||||
|
|
||||||
bool ControlSession::send(const std::string& message) const
|
bool ControlSession::send(const std::string& message) const
|
||||||
{
|
{
|
||||||
//LOG(INFO) << "send: " << message << ", size: " << message.length() << "\n";
|
// LOG(INFO) << "send: " << message << ", size: " << message.length() << "\n";
|
||||||
std::lock_guard<std::recursive_mutex> socketLock(socketMutex_);
|
std::lock_guard<std::recursive_mutex> socketLock(socketMutex_);
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> activeLock(activeMutex_);
|
std::lock_guard<std::recursive_mutex> activeLock(activeMutex_);
|
||||||
|
@ -106,7 +107,7 @@ bool ControlSession::send(const std::string& message) const
|
||||||
std::ostream request_stream(&streambuf);
|
std::ostream request_stream(&streambuf);
|
||||||
request_stream << message << "\r\n";
|
request_stream << message << "\r\n";
|
||||||
asio::write(*socket_.get(), streambuf);
|
asio::write(*socket_.get(), streambuf);
|
||||||
//LOG(INFO) << "done\n";
|
// LOG(INFO) << "done\n";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +134,7 @@ void ControlSession::reader()
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
size_t len = line.length() - 1;
|
size_t len = line.length() - 1;
|
||||||
if ((len >= 2) && line[len-2] == '\r')
|
if ((len >= 2) && line[len - 2] == '\r')
|
||||||
--len;
|
--len;
|
||||||
line.resize(len);
|
line.resize(len);
|
||||||
if ((messageReceiver_ != NULL) && !line.empty())
|
if ((messageReceiver_ != NULL) && !line.empty())
|
||||||
|
@ -170,5 +171,3 @@ void ControlSession::writer()
|
||||||
}
|
}
|
||||||
active_ = false;
|
active_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,16 +19,16 @@
|
||||||
#ifndef CONTROL_SESSION_H
|
#ifndef CONTROL_SESSION_H
|
||||||
#define CONTROL_SESSION_H
|
#define CONTROL_SESSION_H
|
||||||
|
|
||||||
|
#include "common/queue.h"
|
||||||
|
#include "message/message.h"
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
|
||||||
#include <mutex>
|
|
||||||
#include <memory>
|
|
||||||
#include <asio.hpp>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <set>
|
|
||||||
#include "message/message.h"
|
|
||||||
#include "common/queue.h"
|
|
||||||
|
|
||||||
|
|
||||||
using asio::ip::tcp;
|
using asio::ip::tcp;
|
||||||
|
@ -87,9 +87,4 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,12 +19,12 @@
|
||||||
#ifndef ENCODER_H
|
#ifndef ENCODER_H
|
||||||
#define ENCODER_H
|
#define ENCODER_H
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "message/pcmChunk.h"
|
|
||||||
#include "message/codecHeader.h"
|
|
||||||
#include "common/sampleFormat.h"
|
#include "common/sampleFormat.h"
|
||||||
|
#include "message/codecHeader.h"
|
||||||
|
#include "message/pcmChunk.h"
|
||||||
|
|
||||||
|
|
||||||
class Encoder;
|
class Encoder;
|
||||||
|
@ -100,5 +100,3 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,9 +24,9 @@
|
||||||
#if defined(HAS_FLAC)
|
#if defined(HAS_FLAC)
|
||||||
#include "flacEncoder.h"
|
#include "flacEncoder.h"
|
||||||
#endif
|
#endif
|
||||||
#include "common/utils/string_utils.h"
|
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
#include "common/utils/string_utils.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -58,7 +58,7 @@ Encoder* EncoderFactory::createEncoder(const std::string& codecSettings) const
|
||||||
}
|
}
|
||||||
|
|
||||||
return encoder;
|
return encoder;
|
||||||
/* try
|
/* try
|
||||||
{
|
{
|
||||||
encoder->init(NULL, format, codecOptions);
|
encoder->init(NULL, format, codecOptions);
|
||||||
}
|
}
|
||||||
|
@ -67,8 +67,5 @@ Encoder* EncoderFactory::createEncoder(const std::string& codecSettings) const
|
||||||
cout << "Error: " << e.what() << "\n";
|
cout << "Error: " << e.what() << "\n";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
#ifndef ENCODER_FACTORY_H
|
#ifndef ENCODER_FACTORY_H
|
||||||
#define ENCODER_FACTORY_H
|
#define ENCODER_FACTORY_H
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include "encoder.h"
|
#include "encoder.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class EncoderFactory
|
class EncoderFactory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// EncoderFactory(const std::string& codecSettings);
|
// EncoderFactory(const std::string& codecSettings);
|
||||||
Encoder* createEncoder(const std::string& codecSettings) const;
|
Encoder* createEncoder(const std::string& codecSettings) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,10 +18,10 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "flacEncoder.h"
|
|
||||||
#include "common/strCompat.h"
|
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
#include "common/strCompat.h"
|
||||||
|
#include "flacEncoder.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -71,7 +71,8 @@ void FlacEncoder::encode(const msg::PcmChunk* chunk)
|
||||||
{
|
{
|
||||||
int samples = chunk->getSampleCount();
|
int samples = chunk->getSampleCount();
|
||||||
int frames = chunk->getFrameCount();
|
int frames = chunk->getFrameCount();
|
||||||
// LOG(INFO) << "payload: " << chunk->payloadSize << "\tframes: " << frames << "\tsamples: " << samples << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n";
|
// LOG(INFO) << "payload: " << chunk->payloadSize << "\tframes: " << frames << "\tsamples: " << samples << "\tduration: " <<
|
||||||
|
//chunk->duration<chronos::msec>().count() << "\n";
|
||||||
|
|
||||||
if (pcmBufferSize_ < samples)
|
if (pcmBufferSize_ < samples)
|
||||||
{
|
{
|
||||||
|
@ -82,19 +83,19 @@ void FlacEncoder::encode(const msg::PcmChunk* chunk)
|
||||||
if (sampleFormat_.sampleSize == 1)
|
if (sampleFormat_.sampleSize == 1)
|
||||||
{
|
{
|
||||||
FLAC__int8* buffer = (FLAC__int8*)chunk->payload;
|
FLAC__int8* buffer = (FLAC__int8*)chunk->payload;
|
||||||
for(int i=0; i<samples; i++)
|
for (int i = 0; i < samples; i++)
|
||||||
pcmBuffer_[i] = (FLAC__int32)(buffer[i]);
|
pcmBuffer_[i] = (FLAC__int32)(buffer[i]);
|
||||||
}
|
}
|
||||||
else if (sampleFormat_.sampleSize == 2)
|
else if (sampleFormat_.sampleSize == 2)
|
||||||
{
|
{
|
||||||
FLAC__int16* buffer = (FLAC__int16*)chunk->payload;
|
FLAC__int16* buffer = (FLAC__int16*)chunk->payload;
|
||||||
for(int i=0; i<samples; i++)
|
for (int i = 0; i < samples; i++)
|
||||||
pcmBuffer_[i] = (FLAC__int32)(buffer[i]);
|
pcmBuffer_[i] = (FLAC__int32)(buffer[i]);
|
||||||
}
|
}
|
||||||
else if (sampleFormat_.sampleSize == 4)
|
else if (sampleFormat_.sampleSize == 4)
|
||||||
{
|
{
|
||||||
FLAC__int32* buffer = (FLAC__int32*)chunk->payload;
|
FLAC__int32* buffer = (FLAC__int32*)chunk->payload;
|
||||||
for(int i=0; i<samples; i++)
|
for (int i = 0; i < samples; i++)
|
||||||
pcmBuffer_[i] = (FLAC__int32)(buffer[i]);
|
pcmBuffer_[i] = (FLAC__int32)(buffer[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ void FlacEncoder::encode(const msg::PcmChunk* chunk)
|
||||||
if (encodedSamples_ > 0)
|
if (encodedSamples_ > 0)
|
||||||
{
|
{
|
||||||
double resMs = encodedSamples_ / ((double)sampleFormat_.rate / 1000.);
|
double resMs = encodedSamples_ / ((double)sampleFormat_.rate / 1000.);
|
||||||
// LOG(INFO) << "encoded: " << chunk->payloadSize << "\tframes: " << encodedSamples_ << "\tres: " << resMs << "\n";
|
// LOG(INFO) << "encoded: " << chunk->payloadSize << "\tframes: " << encodedSamples_ << "\tres: " << resMs << "\n";
|
||||||
encodedSamples_ = 0;
|
encodedSamples_ = 0;
|
||||||
listener_->onChunkEncoded(this, flacChunk_, resMs);
|
listener_->onChunkEncoded(this, flacChunk_, resMs);
|
||||||
flacChunk_ = new msg::PcmChunk(chunk->format, 0);
|
flacChunk_ = new msg::PcmChunk(chunk->format, 0);
|
||||||
|
@ -112,13 +113,10 @@ void FlacEncoder::encode(const msg::PcmChunk* chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEncoder *encoder,
|
FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples,
|
||||||
const FLAC__byte buffer[],
|
|
||||||
size_t bytes,
|
|
||||||
unsigned samples,
|
|
||||||
unsigned current_frame)
|
unsigned current_frame)
|
||||||
{
|
{
|
||||||
// LOG(INFO) << "write_callback: " << bytes << ", " << samples << ", " << current_frame << "\n";
|
// LOG(INFO) << "write_callback: " << bytes << ", " << samples << ", " << current_frame << "\n";
|
||||||
if ((current_frame == 0) && (bytes > 0) && (samples == 0))
|
if ((current_frame == 0) && (bytes > 0) && (samples == 0))
|
||||||
{
|
{
|
||||||
headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize + bytes);
|
headerChunk_->payload = (char*)realloc(headerChunk_->payload, headerChunk_->payloadSize + bytes);
|
||||||
|
@ -136,12 +134,8 @@ FLAC__StreamEncoderWriteStatus FlacEncoder::write_callback(const FLAC__StreamEnc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder *encoder,
|
FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples,
|
||||||
const FLAC__byte buffer[],
|
unsigned current_frame, void* client_data)
|
||||||
size_t bytes,
|
|
||||||
unsigned samples,
|
|
||||||
unsigned current_frame,
|
|
||||||
void *client_data)
|
|
||||||
{
|
{
|
||||||
FlacEncoder* flacEncoder = (FlacEncoder*)client_data;
|
FlacEncoder* flacEncoder = (FlacEncoder*)client_data;
|
||||||
return flacEncoder->write_callback(encoder, buffer, bytes, samples, current_frame);
|
return flacEncoder->write_callback(encoder, buffer, bytes, samples, current_frame);
|
||||||
|
@ -155,7 +149,7 @@ void FlacEncoder::initEncoder()
|
||||||
{
|
{
|
||||||
quality = cpt::stoi(codecOptions_);
|
quality = cpt::stoi(codecOptions_);
|
||||||
}
|
}
|
||||||
catch(...)
|
catch (...)
|
||||||
{
|
{
|
||||||
throw SnapException("Invalid codec option: \"" + codecOptions_ + "\"");
|
throw SnapException("Invalid codec option: \"" + codecOptions_ + "\"");
|
||||||
}
|
}
|
||||||
|
@ -187,15 +181,13 @@ void FlacEncoder::initEncoder()
|
||||||
throw SnapException("error setting up encoder");
|
throw SnapException("error setting up encoder");
|
||||||
|
|
||||||
// now add some metadata; we'll add some tags and a padding block
|
// now add some metadata; we'll add some tags and a padding block
|
||||||
if (
|
if ((metadata_[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == NULL ||
|
||||||
(metadata_[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == NULL ||
|
|
||||||
(metadata_[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING)) == NULL ||
|
(metadata_[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING)) == NULL ||
|
||||||
// there are many tag (vorbiscomment) functions but these are convenient for this particular use:
|
// there are many tag (vorbiscomment) functions but these are convenient for this particular use:
|
||||||
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "TITLE", "SnapStream") ||
|
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "TITLE", "SnapStream") ||
|
||||||
!FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false) ||
|
!FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false) ||
|
||||||
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "VERSION", VERSION) ||
|
!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, "VERSION", VERSION) ||
|
||||||
!FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false)
|
!FLAC__metadata_object_vorbiscomment_append_comment(metadata_[0], entry, false))
|
||||||
)
|
|
||||||
throw SnapException("out of memory or tag error");
|
throw SnapException("out of memory or tag error");
|
||||||
|
|
||||||
metadata_[1]->length = 1234; // set the padding length
|
metadata_[1]->length = 1234; // set the padding length
|
||||||
|
@ -205,7 +197,6 @@ void FlacEncoder::initEncoder()
|
||||||
|
|
||||||
// initialize encoder
|
// initialize encoder
|
||||||
init_status = FLAC__stream_encoder_init_stream(encoder_, ::write_callback, NULL, NULL, NULL, this);
|
init_status = FLAC__stream_encoder_init_stream(encoder_, ::write_callback, NULL, NULL, NULL, this);
|
||||||
if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
|
if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK)
|
||||||
throw SnapException("ERROR: initializing encoder: " + string(FLAC__StreamEncoderInitStatusString[init_status]));
|
throw SnapException("ERROR: initializing encoder: " + string(FLAC__StreamEncoderInitStatusString[init_status]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -37,15 +37,16 @@ public:
|
||||||
virtual std::string getDefaultOptions() const;
|
virtual std::string getDefaultOptions() const;
|
||||||
virtual std::string name() const;
|
virtual std::string name() const;
|
||||||
|
|
||||||
FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame);
|
FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples,
|
||||||
|
unsigned current_frame);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void initEncoder();
|
virtual void initEncoder();
|
||||||
|
|
||||||
FLAC__StreamEncoder *encoder_;
|
FLAC__StreamEncoder* encoder_;
|
||||||
FLAC__StreamMetadata *metadata_[2];
|
FLAC__StreamMetadata* metadata_[2];
|
||||||
|
|
||||||
FLAC__int32 *pcmBuffer_;
|
FLAC__int32* pcmBuffer_;
|
||||||
int pcmBufferSize_;
|
int pcmBufferSize_;
|
||||||
|
|
||||||
msg::PcmChunk* flacChunk_;
|
msg::PcmChunk* flacChunk_;
|
||||||
|
@ -54,5 +55,3 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,15 +16,15 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "oggEncoder.h"
|
#include "aixlog.hpp"
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
#include "common/strCompat.h"
|
#include "common/strCompat.h"
|
||||||
#include "common/utils/string_utils.h"
|
|
||||||
#include "common/utils.h"
|
#include "common/utils.h"
|
||||||
#include "aixlog.hpp"
|
#include "common/utils/string_utils.h"
|
||||||
|
#include "oggEncoder.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -55,9 +55,10 @@ std::string OggEncoder::name() const
|
||||||
void OggEncoder::encode(const msg::PcmChunk* chunk)
|
void OggEncoder::encode(const msg::PcmChunk* chunk)
|
||||||
{
|
{
|
||||||
double res = 0;
|
double res = 0;
|
||||||
LOG(DEBUG) << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count() << "\n";
|
LOG(DEBUG) << "payload: " << chunk->payloadSize << "\tframes: " << chunk->getFrameCount() << "\tduration: " << chunk->duration<chronos::msec>().count()
|
||||||
|
<< "\n";
|
||||||
int frames = chunk->getFrameCount();
|
int frames = chunk->getFrameCount();
|
||||||
float **buffer=vorbis_analysis_buffer(&vd_, frames);
|
float** buffer = vorbis_analysis_buffer(&vd_, frames);
|
||||||
|
|
||||||
/* uninterleave samples */
|
/* uninterleave samples */
|
||||||
for (size_t channel = 0; channel < sampleFormat_.channels; ++channel)
|
for (size_t channel = 0; channel < sampleFormat_.channels; ++channel)
|
||||||
|
@ -65,20 +66,20 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
|
||||||
if (sampleFormat_.sampleSize == 1)
|
if (sampleFormat_.sampleSize == 1)
|
||||||
{
|
{
|
||||||
int8_t* chunkBuffer = (int8_t*)chunk->payload;
|
int8_t* chunkBuffer = (int8_t*)chunk->payload;
|
||||||
for (int i=0; i<frames; i++)
|
for (int i = 0; i < frames; i++)
|
||||||
buffer[channel][i]= chunkBuffer[sampleFormat_.channels*i + channel] / 128.f;
|
buffer[channel][i] = chunkBuffer[sampleFormat_.channels * i + channel] / 128.f;
|
||||||
}
|
}
|
||||||
else if (sampleFormat_.sampleSize == 2)
|
else if (sampleFormat_.sampleSize == 2)
|
||||||
{
|
{
|
||||||
int16_t* chunkBuffer = (int16_t*)chunk->payload;
|
int16_t* chunkBuffer = (int16_t*)chunk->payload;
|
||||||
for (int i=0; i<frames; i++)
|
for (int i = 0; i < frames; i++)
|
||||||
buffer[channel][i]= chunkBuffer[sampleFormat_.channels*i + channel] / 32768.f;
|
buffer[channel][i] = chunkBuffer[sampleFormat_.channels * i + channel] / 32768.f;
|
||||||
}
|
}
|
||||||
else if (sampleFormat_.sampleSize == 4)
|
else if (sampleFormat_.sampleSize == 4)
|
||||||
{
|
{
|
||||||
int32_t* chunkBuffer = (int32_t*)chunk->payload;
|
int32_t* chunkBuffer = (int32_t*)chunk->payload;
|
||||||
for (int i=0; i<frames; i++)
|
for (int i = 0; i < frames; i++)
|
||||||
buffer[channel][i]= chunkBuffer[sampleFormat_.channels*i + channel] / 2147483648.f;
|
buffer[channel][i] = chunkBuffer[sampleFormat_.channels * i + channel] / 2147483648.f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ void OggEncoder::encode(const msg::PcmChunk* chunk)
|
||||||
more involved (potentially parallel) processing. Get a single
|
more involved (potentially parallel) processing. Get a single
|
||||||
block for encoding now */
|
block for encoding now */
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
while (vorbis_analysis_blockout(&vd_, &vb_)==1)
|
while (vorbis_analysis_blockout(&vd_, &vb_) == 1)
|
||||||
{
|
{
|
||||||
/* analysis, assume we want to use bitrate management */
|
/* analysis, assume we want to use bitrate management */
|
||||||
vorbis_analysis(&vb_, NULL);
|
vorbis_analysis(&vb_, NULL);
|
||||||
|
@ -155,7 +156,7 @@ void OggEncoder::initEncoder()
|
||||||
{
|
{
|
||||||
quality = cpt::stod(qual);
|
quality = cpt::stod(qual);
|
||||||
}
|
}
|
||||||
catch(...)
|
catch (...)
|
||||||
{
|
{
|
||||||
throw SnapException("Invalid codec option: \"" + codecOptions_ + "\"");
|
throw SnapException("Invalid codec option: \"" + codecOptions_ + "\"");
|
||||||
}
|
}
|
||||||
|
@ -256,5 +257,3 @@ void OggEncoder::initEncoder()
|
||||||
pos += og_.body_len;
|
pos += og_.body_len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,8 +19,8 @@
|
||||||
#ifndef OGG_ENCODER_H
|
#ifndef OGG_ENCODER_H
|
||||||
#define OGG_ENCODER_H
|
#define OGG_ENCODER_H
|
||||||
#include "encoder.h"
|
#include "encoder.h"
|
||||||
#include <vorbis/vorbisenc.h>
|
|
||||||
#include <ogg/ogg.h>
|
#include <ogg/ogg.h>
|
||||||
|
#include <vorbis/vorbisenc.h>
|
||||||
|
|
||||||
class OggEncoder : public Encoder
|
class OggEncoder : public Encoder
|
||||||
{
|
{
|
||||||
|
@ -50,5 +50,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,9 +16,9 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include "common/endian.hpp"
|
|
||||||
#include "pcmEncoder.h"
|
#include "pcmEncoder.h"
|
||||||
|
#include "common/endian.hpp"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
|
||||||
#define ID_RIFF 0x46464952
|
#define ID_RIFF 0x46464952
|
||||||
|
@ -65,5 +65,3 @@ std::string PcmEncoder::name() const
|
||||||
{
|
{
|
||||||
return "pcm";
|
return "pcm";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -31,7 +31,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
virtual void initEncoder();
|
virtual void initEncoder();
|
||||||
|
|
||||||
template<typename T>
|
template <typename T>
|
||||||
void assign(void* pointer, T val)
|
void assign(void* pointer, T val)
|
||||||
{
|
{
|
||||||
T* p = (T*)pointer;
|
T* p = (T*)pointer;
|
||||||
|
@ -41,5 +41,3 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,18 +17,17 @@
|
||||||
USA.
|
USA.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include "publishAvahi.h"
|
#include "publishAvahi.h"
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
|
||||||
static AvahiEntryGroup *group;
|
static AvahiEntryGroup* group;
|
||||||
static AvahiSimplePoll *simple_poll;
|
static AvahiSimplePoll* simple_poll;
|
||||||
static char* name;
|
static char* name;
|
||||||
|
|
||||||
PublishAvahi::PublishAvahi(const std::string& serviceName) : PublishmDNS(serviceName),
|
PublishAvahi::PublishAvahi(const std::string& serviceName) : PublishmDNS(serviceName), client_(NULL), active_(false)
|
||||||
client_(NULL), active_(false)
|
|
||||||
{
|
{
|
||||||
group = NULL;
|
group = NULL;
|
||||||
simple_poll = NULL;
|
simple_poll = NULL;
|
||||||
|
@ -43,7 +42,7 @@ void PublishAvahi::publish(const std::vector<mDNSService>& services)
|
||||||
/// Allocate main loop object
|
/// Allocate main loop object
|
||||||
if (!(simple_poll = avahi_simple_poll_new()))
|
if (!(simple_poll = avahi_simple_poll_new()))
|
||||||
{
|
{
|
||||||
///TODO: error handling
|
/// TODO: error handling
|
||||||
LOG(ERROR) << "Failed to create simple poll object.\n";
|
LOG(ERROR) << "Failed to create simple poll object.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +63,8 @@ void PublishAvahi::publish(const std::vector<mDNSService>& services)
|
||||||
|
|
||||||
void PublishAvahi::worker()
|
void PublishAvahi::worker()
|
||||||
{
|
{
|
||||||
while (active_ && (avahi_simple_poll_iterate(simple_poll, 100) == 0));
|
while (active_ && (avahi_simple_poll_iterate(simple_poll, 100) == 0))
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ PublishAvahi::~PublishAvahi()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata)
|
void PublishAvahi::entry_group_callback(AvahiEntryGroup* g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void* userdata)
|
||||||
{
|
{
|
||||||
assert(g == group || group == NULL);
|
assert(g == group || group == NULL);
|
||||||
group = g;
|
group = g;
|
||||||
|
@ -91,14 +91,14 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
|
||||||
/// Called whenever the entry group state changes
|
/// Called whenever the entry group state changes
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case AVAHI_ENTRY_GROUP_ESTABLISHED :
|
case AVAHI_ENTRY_GROUP_ESTABLISHED:
|
||||||
/// The entry group has been established successfully
|
/// The entry group has been established successfully
|
||||||
LOG(INFO) << "Service '" << name << "' successfully established.\n";
|
LOG(INFO) << "Service '" << name << "' successfully established.\n";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AVAHI_ENTRY_GROUP_COLLISION :
|
case AVAHI_ENTRY_GROUP_COLLISION:
|
||||||
{
|
{
|
||||||
char *n;
|
char* n;
|
||||||
|
|
||||||
/// A service name collision with a remote service happened. Let's pick a new name
|
/// A service name collision with a remote service happened. Let's pick a new name
|
||||||
n = avahi_alternative_service_name(name);
|
n = avahi_alternative_service_name(name);
|
||||||
|
@ -112,7 +112,7 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case AVAHI_ENTRY_GROUP_FAILURE :
|
case AVAHI_ENTRY_GROUP_FAILURE:
|
||||||
|
|
||||||
LOG(ERROR) << "Entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) << "\n";
|
LOG(ERROR) << "Entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) << "\n";
|
||||||
|
|
||||||
|
@ -121,15 +121,14 @@ void PublishAvahi::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
||||||
case AVAHI_ENTRY_GROUP_REGISTERING:
|
case AVAHI_ENTRY_GROUP_REGISTERING:;
|
||||||
;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PublishAvahi::create_services(AvahiClient *c)
|
void PublishAvahi::create_services(AvahiClient* c)
|
||||||
{
|
{
|
||||||
assert(c);
|
assert(c);
|
||||||
char *n;
|
char* n;
|
||||||
|
|
||||||
/// If this is the first time we're called, let's create a new entry group if necessary
|
/// If this is the first time we're called, let's create a new entry group if necessary
|
||||||
if (!group)
|
if (!group)
|
||||||
|
@ -148,9 +147,10 @@ void PublishAvahi::create_services(AvahiClient *c)
|
||||||
LOG(INFO) << "Adding service '" << name << "'\n";
|
LOG(INFO) << "Adding service '" << name << "'\n";
|
||||||
|
|
||||||
/// We will now add two services and one subtype to the entry group
|
/// We will now add two services and one subtype to the entry group
|
||||||
for (const auto& service: services_)
|
for (const auto& service : services_)
|
||||||
{
|
{
|
||||||
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, service.name_.c_str(), NULL, NULL, service.port_, NULL)) < 0)
|
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, service.name_.c_str(), NULL, NULL,
|
||||||
|
service.port_, NULL)) < 0)
|
||||||
{
|
{
|
||||||
if (ret == AVAHI_ERR_COLLISION)
|
if (ret == AVAHI_ERR_COLLISION)
|
||||||
goto collision;
|
goto collision;
|
||||||
|
@ -161,12 +161,13 @@ void PublishAvahi::create_services(AvahiClient *c)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an additional (hypothetic) subtype
|
/// Add an additional (hypothetic) subtype
|
||||||
/* if ((ret = avahi_entry_group_add_service_subtype(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, "_printer._tcp", NULL, "_magic._sub._printer._tcp") < 0))
|
/* if ((ret = avahi_entry_group_add_service_subtype(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, "_printer._tcp",
|
||||||
|
NULL, "_magic._sub._printer._tcp") < 0))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Failed to add subtype _magic._sub._printer._tcp: %s\n", avahi_strerror(ret));
|
fprintf(stderr, "Failed to add subtype _magic._sub._printer._tcp: %s\n", avahi_strerror(ret));
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
/// Tell the server to register the service
|
/// Tell the server to register the service
|
||||||
if ((ret = avahi_entry_group_commit(group)) < 0)
|
if ((ret = avahi_entry_group_commit(group)) < 0)
|
||||||
{
|
{
|
||||||
|
@ -196,7 +197,7 @@ fail:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PublishAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata)
|
void PublishAvahi::client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void* userdata)
|
||||||
{
|
{
|
||||||
assert(c);
|
assert(c);
|
||||||
|
|
||||||
|
@ -229,10 +230,6 @@ void PublishAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI
|
||||||
avahi_entry_group_reset(group);
|
avahi_entry_group_reset(group);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AVAHI_CLIENT_CONNECTING:
|
case AVAHI_CLIENT_CONNECTING:;
|
||||||
;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -23,15 +23,15 @@
|
||||||
#include <avahi-client/client.h>
|
#include <avahi-client/client.h>
|
||||||
#include <avahi-client/publish.h>
|
#include <avahi-client/publish.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <avahi-common/alternative.h>
|
#include <avahi-common/alternative.h>
|
||||||
#include <avahi-common/simple-watch.h>
|
|
||||||
#include <avahi-common/malloc.h>
|
|
||||||
#include <avahi-common/error.h>
|
#include <avahi-common/error.h>
|
||||||
|
#include <avahi-common/malloc.h>
|
||||||
|
#include <avahi-common/simple-watch.h>
|
||||||
#include <avahi-common/timeval.h>
|
#include <avahi-common/timeval.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
#include <vector>
|
||||||
|
|
||||||
class PublishAvahi;
|
class PublishAvahi;
|
||||||
|
|
||||||
|
@ -45,9 +45,9 @@ public:
|
||||||
virtual void publish(const std::vector<mDNSService>& services);
|
virtual void publish(const std::vector<mDNSService>& services);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata);
|
static void entry_group_callback(AvahiEntryGroup* g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void* userdata);
|
||||||
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata);
|
static void client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void* userdata);
|
||||||
void create_services(AvahiClient *c);
|
void create_services(AvahiClient* c);
|
||||||
void worker();
|
void worker();
|
||||||
AvahiClient* client_;
|
AvahiClient* client_;
|
||||||
std::thread pollThread_;
|
std::thread pollThread_;
|
||||||
|
@ -57,5 +57,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,16 +19,19 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "publishBonjour.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "publishBonjour.h"
|
||||||
|
|
||||||
typedef union { unsigned char b[2]; unsigned short NotAnInteger; } Opaque16;
|
typedef union {
|
||||||
|
unsigned char b[2];
|
||||||
|
unsigned short NotAnInteger;
|
||||||
|
} Opaque16;
|
||||||
|
|
||||||
|
|
||||||
PublishBonjour::PublishBonjour(const std::string& serviceName) : PublishmDNS(serviceName), active_(false)
|
PublishBonjour::PublishBonjour(const std::string& serviceName) : PublishmDNS(serviceName), active_(false)
|
||||||
{
|
{
|
||||||
/// dns-sd -R Snapcast _snapcast._tcp local 1704
|
/// dns-sd -R Snapcast _snapcast._tcp local 1704
|
||||||
/// dns-sd -R Snapcast _snapcast-jsonrpc._tcp local 1705
|
/// dns-sd -R Snapcast _snapcast-jsonrpc._tcp local 1705
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +39,7 @@ PublishBonjour::~PublishBonjour()
|
||||||
{
|
{
|
||||||
active_ = false;
|
active_ = false;
|
||||||
pollThread_.join();
|
pollThread_.join();
|
||||||
for (auto client: clients)
|
for (auto client : clients)
|
||||||
{
|
{
|
||||||
if (client)
|
if (client)
|
||||||
DNSServiceRefDeallocate(client);
|
DNSServiceRefDeallocate(client);
|
||||||
|
@ -47,7 +50,7 @@ PublishBonjour::~PublishBonjour()
|
||||||
|
|
||||||
void PublishBonjour::worker()
|
void PublishBonjour::worker()
|
||||||
{
|
{
|
||||||
// int dns_sd_fd = client ? DNSServiceRefSockFD(client) : -1;
|
// int dns_sd_fd = client ? DNSServiceRefSockFD(client) : -1;
|
||||||
// 1. Set up the fd_set as usual here.
|
// 1. Set up the fd_set as usual here.
|
||||||
// This example client has no file descriptors of its own,
|
// This example client has no file descriptors of its own,
|
||||||
// but a real application would call FD_SET to add them to the set here
|
// but a real application would call FD_SET to add them to the set here
|
||||||
|
@ -56,7 +59,7 @@ void PublishBonjour::worker()
|
||||||
|
|
||||||
std::vector<int> dns_sd_fds;
|
std::vector<int> dns_sd_fds;
|
||||||
int nfds = -1;
|
int nfds = -1;
|
||||||
for (size_t n=0; n<clients.size(); ++n)
|
for (size_t n = 0; n < clients.size(); ++n)
|
||||||
{
|
{
|
||||||
int dns_sd_fd = DNSServiceRefSockFD(clients[n]);
|
int dns_sd_fd = DNSServiceRefSockFD(clients[n]);
|
||||||
dns_sd_fds.push_back(dns_sd_fd);
|
dns_sd_fds.push_back(dns_sd_fd);
|
||||||
|
@ -70,20 +73,20 @@ void PublishBonjour::worker()
|
||||||
// 3. Set up the timeout.
|
// 3. Set up the timeout.
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = 0;
|
||||||
tv.tv_usec = 100*1000;
|
tv.tv_usec = 100 * 1000;
|
||||||
|
|
||||||
active_ = true;
|
active_ = true;
|
||||||
while (active_)
|
while (active_)
|
||||||
{
|
{
|
||||||
FD_ZERO(&readfds);
|
FD_ZERO(&readfds);
|
||||||
for (size_t n=0; n<dns_sd_fds.size(); ++n)
|
for (size_t n = 0; n < dns_sd_fds.size(); ++n)
|
||||||
FD_SET(dns_sd_fds[n], &readfds);
|
FD_SET(dns_sd_fds[n], &readfds);
|
||||||
|
|
||||||
int result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
|
int result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
|
||||||
if (result > 0)
|
if (result > 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
for (size_t n=0; n<dns_sd_fds.size(); ++n)
|
for (size_t n = 0; n < dns_sd_fds.size(); ++n)
|
||||||
{
|
{
|
||||||
if (clients[n] && FD_ISSET(dns_sd_fds[n], &readfds))
|
if (clients[n] && FD_ISSET(dns_sd_fds[n], &readfds))
|
||||||
{
|
{
|
||||||
|
@ -96,8 +99,8 @@ void PublishBonjour::worker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// else if (result == 0)
|
// else if (result == 0)
|
||||||
// myTimerCallBack();
|
// myTimerCallBack();
|
||||||
else if (result < 0)
|
else if (result < 0)
|
||||||
{
|
{
|
||||||
LOG(ERROR) << "select() returned " << result << " errno " << errno << " " << strerror(errno) << "\n";
|
LOG(ERROR) << "select() returned " << result << " errno " << errno << " " << strerror(errno) << "\n";
|
||||||
|
@ -108,7 +111,8 @@ void PublishBonjour::worker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void DNSSD_API reg_reply(DNSServiceRef sdref, const DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context)
|
static void DNSSD_API reg_reply(DNSServiceRef sdref, const DNSServiceFlags flags, DNSServiceErrorType errorCode, const char* name, const char* regtype,
|
||||||
|
const char* domain, void* context)
|
||||||
{
|
{
|
||||||
(void)sdref; // Unused
|
(void)sdref; // Unused
|
||||||
(void)flags; // Unused
|
(void)flags; // Unused
|
||||||
|
@ -141,19 +145,17 @@ static void DNSSD_API reg_reply(DNSServiceRef sdref, const DNSServiceFlags flags
|
||||||
|
|
||||||
void PublishBonjour::publish(const std::vector<mDNSService>& services)
|
void PublishBonjour::publish(const std::vector<mDNSService>& services)
|
||||||
{
|
{
|
||||||
for (auto service: services)
|
for (auto service : services)
|
||||||
{
|
{
|
||||||
DNSServiceFlags flags = 0;
|
DNSServiceFlags flags = 0;
|
||||||
Opaque16 registerPort = { { static_cast<unsigned char>(service.port_ >> 8), static_cast<unsigned char>(service.port_ & 0xFF) } };
|
Opaque16 registerPort = {{static_cast<unsigned char>(service.port_ >> 8), static_cast<unsigned char>(service.port_ & 0xFF)}};
|
||||||
DNSServiceRef client = NULL;
|
DNSServiceRef client = NULL;
|
||||||
// DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL, registerPort.NotAnInteger, service.txt_.size(), service.txt_.empty()?NULL:service.txt_.c_str(), reg_reply, this);
|
// DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL,
|
||||||
DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL, registerPort.NotAnInteger, 0, NULL, reg_reply, this);
|
//registerPort.NotAnInteger, service.txt_.size(), service.txt_.empty()?NULL:service.txt_.c_str(), reg_reply, this);
|
||||||
|
DNSServiceRegister(&client, flags, kDNSServiceInterfaceIndexAny, serviceName_.c_str(), service.name_.c_str(), NULL, NULL, registerPort.NotAnInteger, 0,
|
||||||
|
NULL, reg_reply, this);
|
||||||
clients.push_back(client);
|
clients.push_back(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
pollThread_ = std::thread(&PublishBonjour::worker, this);
|
pollThread_ = std::thread(&PublishBonjour::worker, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,8 +20,8 @@
|
||||||
#ifndef PUBLISH_BONJOUR_H
|
#ifndef PUBLISH_BONJOUR_H
|
||||||
#define PUBLISH_BONJOUR_H
|
#define PUBLISH_BONJOUR_H
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <dns_sd.h>
|
#include <dns_sd.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class PublishBonjour;
|
class PublishBonjour;
|
||||||
|
|
||||||
|
@ -43,5 +43,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
0
server/publishZeroConf/publishmDNS.h
Executable file → Normal file
0
server/publishZeroConf/publishmDNS.h
Executable file → Normal file
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,19 +24,19 @@
|
||||||
#ifdef HAS_DAEMON
|
#ifdef HAS_DAEMON
|
||||||
#include "common/daemon.h"
|
#include "common/daemon.h"
|
||||||
#endif
|
#endif
|
||||||
#include "common/timeDefs.h"
|
#include "common/sampleFormat.h"
|
||||||
#include "common/utils/string_utils.h"
|
|
||||||
#include "common/signalHandler.h"
|
#include "common/signalHandler.h"
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
#include "common/sampleFormat.h"
|
#include "common/timeDefs.h"
|
||||||
#include "message/message.h"
|
#include "common/utils/string_utils.h"
|
||||||
#include "encoder/encoderFactory.h"
|
#include "encoder/encoderFactory.h"
|
||||||
|
#include "message/message.h"
|
||||||
#include "streamServer.h"
|
#include "streamServer.h"
|
||||||
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
||||||
#include "publishZeroConf/publishmDNS.h"
|
#include "publishZeroConf/publishmDNS.h"
|
||||||
#endif
|
#endif
|
||||||
#include "config.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
volatile sig_atomic_t g_terminated = false;
|
volatile sig_atomic_t g_terminated = false;
|
||||||
|
@ -63,15 +63,18 @@ int main(int argc, char* argv[])
|
||||||
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message");
|
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message");
|
||||||
auto debugOption = op.add<Implicit<string>, Attribute::hidden>("", "debug", "enable debug logging", "");
|
auto debugOption = op.add<Implicit<string>, Attribute::hidden>("", "debug", "enable debug logging", "");
|
||||||
auto versionSwitch = op.add<Switch>("v", "version", "Show version number");
|
auto versionSwitch = op.add<Switch>("v", "version", "Show version number");
|
||||||
/*auto portValue =*/ op.add<Value<size_t>>("p", "port", "Server port", settings.port, &settings.port);
|
/*auto portValue =*/op.add<Value<size_t>>("p", "port", "Server port", settings.port, &settings.port);
|
||||||
/*auto controlPortValue =*/ op.add<Value<size_t>>("", "controlPort", "Remote control port", settings.controlPort, &settings.controlPort);
|
/*auto controlPortValue =*/op.add<Value<size_t>>("", "controlPort", "Remote control port", settings.controlPort, &settings.controlPort);
|
||||||
auto streamValue = op.add<Value<string>>("s", "stream", "URI of the PCM input stream.\nFormat: TYPE://host/path?name=NAME\n[&codec=CODEC]\n[&sampleformat=SAMPLEFORMAT]", pcmStream, &pcmStream);
|
auto streamValue = op.add<Value<string>>(
|
||||||
|
"s", "stream", "URI of the PCM input stream.\nFormat: TYPE://host/path?name=NAME\n[&codec=CODEC]\n[&sampleformat=SAMPLEFORMAT]", pcmStream,
|
||||||
|
&pcmStream);
|
||||||
|
|
||||||
/*auto sampleFormatValue =*/ op.add<Value<string>>("", "sampleformat", "Default sample format", settings.sampleFormat, &settings.sampleFormat);
|
/*auto sampleFormatValue =*/op.add<Value<string>>("", "sampleformat", "Default sample format", settings.sampleFormat, &settings.sampleFormat);
|
||||||
/*auto codecValue =*/ op.add<Value<string>>("c", "codec", "Default transport codec\n(flac|ogg|pcm)[:options]\nType codec:? to get codec specific options", settings.codec, &settings.codec);
|
/*auto codecValue =*/op.add<Value<string>>(
|
||||||
/*auto streamBufferValue =*/ op.add<Value<size_t>>("", "streamBuffer", "Default stream read buffer [ms]", settings.streamReadMs, &settings.streamReadMs);
|
"c", "codec", "Default transport codec\n(flac|ogg|pcm)[:options]\nType codec:? to get codec specific options", settings.codec, &settings.codec);
|
||||||
/*auto bufferValue =*/ op.add<Value<int>>("b", "buffer", "Buffer [ms]", settings.bufferMs, &settings.bufferMs);
|
/*auto streamBufferValue =*/op.add<Value<size_t>>("", "streamBuffer", "Default stream read buffer [ms]", settings.streamReadMs, &settings.streamReadMs);
|
||||||
/*auto muteSwitch =*/ op.add<Switch>("", "sendToMuted", "Send audio to muted clients", &settings.sendAudioToMutedClients);
|
/*auto bufferValue =*/op.add<Value<int>>("b", "buffer", "Buffer [ms]", settings.bufferMs, &settings.bufferMs);
|
||||||
|
/*auto muteSwitch =*/op.add<Switch>("", "sendToMuted", "Send audio to muted clients", &settings.sendAudioToMutedClients);
|
||||||
#ifdef HAS_DAEMON
|
#ifdef HAS_DAEMON
|
||||||
int processPriority(0);
|
int processPriority(0);
|
||||||
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "Daemonize\noptional process priority [-20..19]", 0, &processPriority);
|
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "Daemonize\noptional process priority [-20..19]", 0, &processPriority);
|
||||||
|
@ -116,7 +119,7 @@ int main(int argc, char* argv[])
|
||||||
if (!streamValue->is_set())
|
if (!streamValue->is_set())
|
||||||
settings.pcmStreams.push_back(streamValue->value());
|
settings.pcmStreams.push_back(streamValue->value());
|
||||||
|
|
||||||
for (size_t n=0; n<streamValue->count(); ++n)
|
for (size_t n = 0; n < streamValue->count(); ++n)
|
||||||
{
|
{
|
||||||
cout << streamValue->value(n) << "\n";
|
cout << streamValue->value(n) << "\n";
|
||||||
settings.pcmStreams.push_back(streamValue->value(n));
|
settings.pcmStreams.push_back(streamValue->value(n));
|
||||||
|
@ -140,7 +143,8 @@ int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
AixLog::Log::instance().add_logsink<AixLog::SinkCout>(AixLog::Severity::trace, AixLog::Type::all, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
||||||
if (!debugOption->value().empty())
|
if (!debugOption->value().empty())
|
||||||
AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(), "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
AixLog::Log::instance().add_logsink<AixLog::SinkFile>(AixLog::Severity::trace, AixLog::Type::all, debugOption->value(),
|
||||||
|
"%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -190,11 +194,8 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
||||||
PublishZeroConf publishZeroConfg("Snapcast");
|
PublishZeroConf publishZeroConfg("Snapcast");
|
||||||
publishZeroConfg.publish({
|
publishZeroConfg.publish({mDNSService("_snapcast._tcp", settings.port), mDNSService("_snapcast-jsonrpc._tcp", settings.controlPort),
|
||||||
mDNSService("_snapcast._tcp", settings.port),
|
mDNSService("_snapcastjsonrpc._tcp", settings.controlPort)});
|
||||||
mDNSService("_snapcast-jsonrpc._tcp", settings.controlPort),
|
|
||||||
mDNSService("_snapcastjsonrpc._tcp", settings.controlPort)
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (settings.bufferMs < 400)
|
if (settings.bufferMs < 400)
|
||||||
|
@ -204,7 +205,7 @@ int main(int argc, char* argv[])
|
||||||
std::unique_ptr<StreamServer> streamServer(new StreamServer(&io_service, settings));
|
std::unique_ptr<StreamServer> streamServer(new StreamServer(&io_service, settings));
|
||||||
streamServer->start();
|
streamServer->start();
|
||||||
|
|
||||||
auto func = [](asio::io_service* ioservice)->void{ioservice->run();};
|
auto func = [](asio::io_service* ioservice) -> void { ioservice->run(); };
|
||||||
std::thread t(func, &io_service);
|
std::thread t(func, &io_service);
|
||||||
|
|
||||||
while (!g_terminated)
|
while (!g_terminated)
|
||||||
|
@ -226,4 +227,3 @@ int main(int argc, char* argv[])
|
||||||
SLOG(NOTICE) << "daemon terminated." << endl;
|
SLOG(NOTICE) << "daemon terminated." << endl;
|
||||||
exit(exitcode);
|
exit(exitcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include "streamServer.h"
|
#include "streamServer.h"
|
||||||
#include "message/time.h"
|
|
||||||
#include "message/hello.h"
|
|
||||||
#include "message/streamTags.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "message/hello.h"
|
||||||
|
#include "message/streamTags.h"
|
||||||
|
#include "message/time.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -29,11 +29,8 @@ using namespace std;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
|
||||||
StreamServer::StreamServer(asio::io_service* io_service, const StreamServerSettings& streamServerSettings) :
|
StreamServer::StreamServer(asio::io_service* io_service, const StreamServerSettings& streamServerSettings)
|
||||||
io_service_(io_service),
|
: io_service_(io_service), acceptor_v4_(nullptr), acceptor_v6_(nullptr), settings_(streamServerSettings)
|
||||||
acceptor_v4_(nullptr),
|
|
||||||
acceptor_v6_(nullptr),
|
|
||||||
settings_(streamServerSettings)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,11 +42,12 @@ StreamServer::~StreamServer()
|
||||||
|
|
||||||
void StreamServer::onMetaChanged(const PcmStream* pcmStream)
|
void StreamServer::onMetaChanged(const PcmStream* pcmStream)
|
||||||
{
|
{
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Stream.OnMetadata","params":{"id":"stream 1", "meta": {"album": "some album", "artist": "some artist", "track": "some track"...}}
|
/// Notification: {"jsonrpc":"2.0","method":"Stream.OnMetadata","params":{"id":"stream 1", "meta": {"album": "some album", "artist": "some artist", "track":
|
||||||
|
/// "some track"...}}
|
||||||
|
|
||||||
// Send meta to all connected clients
|
// Send meta to all connected clients
|
||||||
const auto meta = pcmStream->getMeta();
|
const auto meta = pcmStream->getMeta();
|
||||||
//cout << "metadata = " << meta->msg.dump(3) << "\n";
|
// cout << "metadata = " << meta->msg.dump(3) << "\n";
|
||||||
|
|
||||||
for (auto s : sessions_)
|
for (auto s : sessions_)
|
||||||
{
|
{
|
||||||
|
@ -65,9 +63,11 @@ void StreamServer::onMetaChanged(const PcmStream* pcmStream)
|
||||||
|
|
||||||
void StreamServer::onStateChanged(const PcmStream* pcmStream, const ReaderState& state)
|
void StreamServer::onStateChanged(const PcmStream* pcmStream, const ReaderState& state)
|
||||||
{
|
{
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Stream.OnUpdate","params":{"id":"stream 1","stream":{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}}}}
|
/// Notification: {"jsonrpc":"2.0","method":"Stream.OnUpdate","params":{"id":"stream 1","stream":{"id":"stream
|
||||||
|
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}}}}
|
||||||
LOG(INFO) << "onStateChanged (" << pcmStream->getName() << "): " << state << "\n";
|
LOG(INFO) << "onStateChanged (" << pcmStream->getName() << "): " << state << "\n";
|
||||||
// LOG(INFO) << pcmStream->toJson().dump(4);
|
// LOG(INFO) << pcmStream->toJson().dump(4);
|
||||||
json notification = jsonrpcpp::Notification("Stream.OnUpdate", jsonrpcpp::Parameter("id", pcmStream->getId(), "stream", pcmStream->toJson())).to_json();
|
json notification = jsonrpcpp::Notification("Stream.OnUpdate", jsonrpcpp::Parameter("id", pcmStream->getId(), "stream", pcmStream->toJson())).to_json();
|
||||||
controlServer_->send(notification.dump(), NULL);
|
controlServer_->send(notification.dump(), NULL);
|
||||||
////cout << "Notification: " << notification.dump() << "\n";
|
////cout << "Notification: " << notification.dump() << "\n";
|
||||||
|
@ -76,7 +76,7 @@ void StreamServer::onStateChanged(const PcmStream* pcmStream, const ReaderState&
|
||||||
|
|
||||||
void StreamServer::onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration)
|
void StreamServer::onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk, double duration)
|
||||||
{
|
{
|
||||||
// LOG(INFO) << "onChunkRead (" << pcmStream->getName() << "): " << duration << "ms\n";
|
// LOG(INFO) << "onChunkRead (" << pcmStream->getName() << "): " << duration << "ms\n";
|
||||||
bool isDefaultStream(pcmStream == streamManager_->getDefaultStream().get());
|
bool isDefaultStream(pcmStream == streamManager_->getDefaultStream().get());
|
||||||
|
|
||||||
msg::message_ptr shared_message(chunk);
|
msg::message_ptr shared_message(chunk);
|
||||||
|
@ -97,7 +97,7 @@ void StreamServer::onChunkRead(const PcmStream* pcmStream, msg::PcmChunk* chunk,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!s->pcmStream() && isDefaultStream)//->getName() == "default")
|
if (!s->pcmStream() && isDefaultStream) //->getName() == "default")
|
||||||
s->sendAsync(shared_message);
|
s->sendAsync(shared_message);
|
||||||
else if (s->pcmStream().get() == pcmStream)
|
else if (s->pcmStream().get() == pcmStream)
|
||||||
s->sendAsync(shared_message);
|
s->sendAsync(shared_message);
|
||||||
|
@ -122,7 +122,7 @@ void StreamServer::onDisconnect(StreamSession* streamSession)
|
||||||
LOG(INFO) << "onDisconnect: " << session->clientId << "\n";
|
LOG(INFO) << "onDisconnect: " << session->clientId << "\n";
|
||||||
LOG(DEBUG) << "sessions: " << sessions_.size() << "\n";
|
LOG(DEBUG) << "sessions: " << sessions_.size() << "\n";
|
||||||
// don't block: remove StreamSession in a thread
|
// don't block: remove StreamSession in a thread
|
||||||
auto func = [](shared_ptr<StreamSession> s)->void{s->stop();};
|
auto func = [](shared_ptr<StreamSession> s) -> void { s->stop(); };
|
||||||
std::thread t(func, session);
|
std::thread t(func, session);
|
||||||
t.detach();
|
t.detach();
|
||||||
sessions_.erase(session);
|
sessions_.erase(session);
|
||||||
|
@ -144,8 +144,12 @@ void StreamServer::onDisconnect(StreamSession* streamSession)
|
||||||
/// in case of a duplicate client id
|
/// in case of a duplicate client id
|
||||||
if (getStreamSession(clientInfo->id) == nullptr)
|
if (getStreamSession(clientInfo->id) == nullptr)
|
||||||
{
|
{
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Client.OnDisconnect","params":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":false,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025523,"usec":814067},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},"id":"00:21:6a:7d:74:fc"}}
|
/// Notification:
|
||||||
json notification = jsonrpcpp::Notification("Client.OnDisconnect", jsonrpcpp::Parameter("id", clientInfo->id, "client", clientInfo->toJson())).to_json();
|
/// {"jsonrpc":"2.0","method":"Client.OnDisconnect","params":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":false,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025523,"usec":814067},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},"id":"00:21:6a:7d:74:fc"}}
|
||||||
|
json notification =
|
||||||
|
jsonrpcpp::Notification("Client.OnDisconnect", jsonrpcpp::Parameter("id", clientInfo->id, "client", clientInfo->toJson())).to_json();
|
||||||
controlServer_->send(notification.dump());
|
controlServer_->send(notification.dump());
|
||||||
////cout << "Notification: " << notification.dump() << "\n";
|
////cout << "Notification: " << notification.dump() << "\n";
|
||||||
}
|
}
|
||||||
|
@ -169,7 +173,10 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
if (request->method() == "Client.GetStatus")
|
if (request->method() == "Client.GetStatus")
|
||||||
{
|
{
|
||||||
/// Request: {"id":8,"jsonrpc":"2.0","method":"Client.GetStatus","params":{"id":"00:21:6a:7d:74:fc"}}
|
/// Request: {"id":8,"jsonrpc":"2.0","method":"Client.GetStatus","params":{"id":"00:21:6a:7d:74:fc"}}
|
||||||
/// Response: {"id":8,"jsonrpc":"2.0","result":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026416,"usec":135973},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}}}
|
/// Response:
|
||||||
|
/// {"id":8,"jsonrpc":"2.0","result":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026416,"usec":135973},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}}}
|
||||||
result["client"] = clientInfo->toJson();
|
result["client"] = clientInfo->toJson();
|
||||||
}
|
}
|
||||||
else if (request->method() == "Client.SetVolume")
|
else if (request->method() == "Client.SetVolume")
|
||||||
|
@ -179,7 +186,8 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Client.OnVolumeChanged","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}}
|
/// Notification: {"jsonrpc":"2.0","method":"Client.OnVolumeChanged","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}}
|
||||||
clientInfo->config.volume.fromJson(request->params().get("volume"));
|
clientInfo->config.volume.fromJson(request->params().get("volume"));
|
||||||
result["volume"] = clientInfo->config.volume.toJson();
|
result["volume"] = clientInfo->config.volume.toJson();
|
||||||
notification.reset(new jsonrpcpp::Notification("Client.OnVolumeChanged", jsonrpcpp::Parameter("id", clientInfo->id, "volume", clientInfo->config.volume.toJson())));
|
notification.reset(new jsonrpcpp::Notification("Client.OnVolumeChanged",
|
||||||
|
jsonrpcpp::Parameter("id", clientInfo->id, "volume", clientInfo->config.volume.toJson())));
|
||||||
}
|
}
|
||||||
else if (request->method() == "Client.SetLatency")
|
else if (request->method() == "Client.SetLatency")
|
||||||
{
|
{
|
||||||
|
@ -193,7 +201,8 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
latency = settings_.bufferMs;
|
latency = settings_.bufferMs;
|
||||||
clientInfo->config.latency = latency; //, -10000, settings_.bufferMs);
|
clientInfo->config.latency = latency; //, -10000, settings_.bufferMs);
|
||||||
result["latency"] = clientInfo->config.latency;
|
result["latency"] = clientInfo->config.latency;
|
||||||
notification.reset(new jsonrpcpp::Notification("Client.OnLatencyChanged", jsonrpcpp::Parameter("id", clientInfo->id, "latency", clientInfo->config.latency)));
|
notification.reset(
|
||||||
|
new jsonrpcpp::Notification("Client.OnLatencyChanged", jsonrpcpp::Parameter("id", clientInfo->id, "latency", clientInfo->config.latency)));
|
||||||
}
|
}
|
||||||
else if (request->method() == "Client.SetName")
|
else if (request->method() == "Client.SetName")
|
||||||
{
|
{
|
||||||
|
@ -202,7 +211,8 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Client.OnNameChanged","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}}
|
/// Notification: {"jsonrpc":"2.0","method":"Client.OnNameChanged","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}}
|
||||||
clientInfo->config.name = request->params().get<std::string>("name");
|
clientInfo->config.name = request->params().get<std::string>("name");
|
||||||
result["name"] = clientInfo->config.name;
|
result["name"] = clientInfo->config.name;
|
||||||
notification.reset(new jsonrpcpp::Notification("Client.OnNameChanged", jsonrpcpp::Parameter("id", clientInfo->id, "name", clientInfo->config.name)));
|
notification.reset(
|
||||||
|
new jsonrpcpp::Notification("Client.OnNameChanged", jsonrpcpp::Parameter("id", clientInfo->id, "name", clientInfo->config.name)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw jsonrpcpp::MethodNotFoundException(request->id());
|
throw jsonrpcpp::MethodNotFoundException(request->id());
|
||||||
|
@ -233,7 +243,13 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
if (request->method() == "Group.GetStatus")
|
if (request->method() == "Group.GetStatus")
|
||||||
{
|
{
|
||||||
/// Request: {"id":5,"jsonrpc":"2.0","method":"Group.GetStatus","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
|
/// Request: {"id":5,"jsonrpc":"2.0","method":"Group.GetStatus","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
|
||||||
/// Response: {"id":5,"jsonrpc":"2.0","result":{"group":{"clients":[{"config":{"instance":2,"latency":10,"name":"Laptop","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488026485,"usec":644997},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026481,"usec":223747},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":true,"name":"","stream_id":"stream 1"}}}
|
/// Response:
|
||||||
|
/// {"id":5,"jsonrpc":"2.0","result":{"group":{"clients":[{"config":{"instance":2,"latency":10,"name":"Laptop","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488026485,"usec":644997},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026481,"usec":223747},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":true,"name":"","stream_id":"stream
|
||||||
|
/// 1"}}}
|
||||||
result["group"] = group->toJson();
|
result["group"] = group->toJson();
|
||||||
}
|
}
|
||||||
else if (request->method() == "Group.SetMute")
|
else if (request->method() == "Group.SetMute")
|
||||||
|
@ -245,7 +261,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
group->muted = muted;
|
group->muted = muted;
|
||||||
|
|
||||||
/// Update clients
|
/// Update clients
|
||||||
for (auto client: group->clients)
|
for (auto client : group->clients)
|
||||||
{
|
{
|
||||||
session_ptr session = getStreamSession(client->id);
|
session_ptr session = getStreamSession(client->id);
|
||||||
if (session != nullptr)
|
if (session != nullptr)
|
||||||
|
@ -265,9 +281,11 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
}
|
}
|
||||||
else if (request->method() == "Group.SetStream")
|
else if (request->method() == "Group.SetStream")
|
||||||
{
|
{
|
||||||
/// Request: {"id":4,"jsonrpc":"2.0","method":"Group.SetStream","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream 1"}}
|
/// Request: {"id":4,"jsonrpc":"2.0","method":"Group.SetStream","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream
|
||||||
|
/// 1"}}
|
||||||
/// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"stream 1"}}
|
/// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"stream 1"}}
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Group.OnStreamChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream 1"}}
|
/// Notification: {"jsonrpc":"2.0","method":"Group.OnStreamChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream
|
||||||
|
/// 1"}}
|
||||||
string streamId = request->params().get<std::string>("stream_id");
|
string streamId = request->params().get<std::string>("stream_id");
|
||||||
PcmStreamPtr stream = streamManager_->getStream(streamId);
|
PcmStreamPtr stream = streamManager_->getStream(streamId);
|
||||||
if (stream == nullptr)
|
if (stream == nullptr)
|
||||||
|
@ -276,7 +294,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
group->streamId = streamId;
|
group->streamId = streamId;
|
||||||
|
|
||||||
/// Update clients
|
/// Update clients
|
||||||
for (auto client: group->clients)
|
for (auto client : group->clients)
|
||||||
{
|
{
|
||||||
session_ptr session = getStreamSession(client->id);
|
session_ptr session = getStreamSession(client->id);
|
||||||
if (session && (session->pcmStream() != stream))
|
if (session && (session->pcmStream() != stream))
|
||||||
|
@ -293,9 +311,33 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
}
|
}
|
||||||
else if (request->method() == "Group.SetClients")
|
else if (request->method() == "Group.SetClients")
|
||||||
{
|
{
|
||||||
/// Request: {"id":3,"jsonrpc":"2.0","method":"Group.SetClients","params":{"clients":["00:21:6a:7d:74:fc#2","00:21:6a:7d:74:fc"],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
|
/// Request:
|
||||||
/// Response: {"id":3,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
/// {"id":3,"jsonrpc":"2.0","method":"Group.SetClients","params":{"clients":["00:21:6a:7d:74:fc#2","00:21:6a:7d:74:fc"],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
/// Response: {"id":3,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
|
||||||
|
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
|
||||||
|
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
|
||||||
|
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
|
||||||
|
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
|
||||||
|
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
|
/// Notification:
|
||||||
|
/// {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
|
||||||
|
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
|
||||||
|
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
|
||||||
|
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
|
||||||
|
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
|
||||||
|
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
vector<string> clients = request->params().get("clients");
|
vector<string> clients = request->params().get("clients");
|
||||||
/// Remove clients from group
|
/// Remove clients from group
|
||||||
for (auto iter = group->clients.begin(); iter != group->clients.end();)
|
for (auto iter = group->clients.begin(); iter != group->clients.end();)
|
||||||
|
@ -313,7 +355,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
|
|
||||||
/// Add clients to group
|
/// Add clients to group
|
||||||
PcmStreamPtr stream = streamManager_->getStream(group->streamId);
|
PcmStreamPtr stream = streamManager_->getStream(group->streamId);
|
||||||
for (const auto& clientId: clients)
|
for (const auto& clientId : clients)
|
||||||
{
|
{
|
||||||
ClientInfoPtr client = Config::instance().getClientInfo(clientId);
|
ClientInfoPtr client = Config::instance().getClientInfo(clientId);
|
||||||
if (!client)
|
if (!client)
|
||||||
|
@ -368,14 +410,44 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
else if (request->method() == "Server.GetStatus")
|
else if (request->method() == "Server.GetStatus")
|
||||||
{
|
{
|
||||||
/// Request: {"id":1,"jsonrpc":"2.0","method":"Server.GetStatus"}
|
/// Request: {"id":1,"jsonrpc":"2.0","method":"Server.GetStatus"}
|
||||||
/// Response: {"id":1,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025696,"usec":578142},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025696,"usec":611255},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
/// Response: {"id":1,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
|
||||||
|
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025696,"usec":578142},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025696,"usec":611255},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
|
||||||
|
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
|
||||||
|
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
|
||||||
|
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
|
||||||
|
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
result["server"] = Config::instance().getServerStatus(streamManager_->toJson());
|
result["server"] = Config::instance().getServerStatus(streamManager_->toJson());
|
||||||
}
|
}
|
||||||
else if (request->method() == "Server.DeleteClient")
|
else if (request->method() == "Server.DeleteClient")
|
||||||
{
|
{
|
||||||
/// Request: {"id":2,"jsonrpc":"2.0","method":"Server.DeleteClient","params":{"id":"00:21:6a:7d:74:fc"}}
|
/// Request: {"id":2,"jsonrpc":"2.0","method":"Server.DeleteClient","params":{"id":"00:21:6a:7d:74:fc"}}
|
||||||
/// Response: {"id":2,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
/// Response: {"id":2,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
|
||||||
|
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
|
||||||
|
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
|
||||||
|
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
|
||||||
|
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
|
/// Notification:
|
||||||
|
/// {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
|
||||||
|
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
|
||||||
|
/// 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
|
||||||
|
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
|
||||||
|
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
|
||||||
|
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get<std::string>("id"));
|
ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get<std::string>("id"));
|
||||||
if (clientInfo == nullptr)
|
if (clientInfo == nullptr)
|
||||||
throw jsonrpcpp::InternalErrorException("Client not found", request->id());
|
throw jsonrpcpp::InternalErrorException("Client not found", request->id());
|
||||||
|
@ -401,7 +473,7 @@ void StreamServer::ProcessRequest(const jsonrpcpp::request_ptr request, jsonrpcp
|
||||||
/// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"Spotify"}}
|
/// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"Spotify"}}
|
||||||
/// Call onMetaChanged(const PcmStream* pcmStream) for updates and notifications
|
/// Call onMetaChanged(const PcmStream* pcmStream) for updates and notifications
|
||||||
|
|
||||||
LOG(INFO) << "Stream.SetMeta(" << request->params().get<std::string>("id") << ")" << request->params().get("meta") <<"\n";
|
LOG(INFO) << "Stream.SetMeta(" << request->params().get<std::string>("id") << ")" << request->params().get("meta") << "\n";
|
||||||
|
|
||||||
// Find stream
|
// Find stream
|
||||||
string streamId = request->params().get<std::string>("id");
|
string streamId = request->params().get<std::string>("id");
|
||||||
|
@ -447,12 +519,12 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std::
|
||||||
if (!entity)
|
if (!entity)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch(const jsonrpcpp::ParseErrorException& e)
|
catch (const jsonrpcpp::ParseErrorException& e)
|
||||||
{
|
{
|
||||||
controlSession->send(e.to_json().dump());
|
controlSession->send(e.to_json().dump());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch(const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
controlSession->send(jsonrpcpp::ParseErrorException(e.what()).to_json().dump());
|
controlSession->send(jsonrpcpp::ParseErrorException(e.what()).to_json().dump());
|
||||||
return;
|
return;
|
||||||
|
@ -482,7 +554,7 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std::
|
||||||
////cout << "Batch: " << batch->to_json().dump() << "\n";
|
////cout << "Batch: " << batch->to_json().dump() << "\n";
|
||||||
jsonrpcpp::Batch responseBatch;
|
jsonrpcpp::Batch responseBatch;
|
||||||
jsonrpcpp::Batch notificationBatch;
|
jsonrpcpp::Batch notificationBatch;
|
||||||
for (const auto& batch_entity: batch->entities)
|
for (const auto& batch_entity : batch->entities)
|
||||||
{
|
{
|
||||||
if (batch_entity->is_request())
|
if (batch_entity->is_request())
|
||||||
{
|
{
|
||||||
|
@ -505,14 +577,17 @@ void StreamServer::onMessageReceived(ControlSession* controlSession, const std::
|
||||||
|
|
||||||
void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::BaseMessage& baseMessage, char* buffer)
|
void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::BaseMessage& baseMessage, char* buffer)
|
||||||
{
|
{
|
||||||
// LOG(DEBUG) << "onMessageReceived: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << ", sent: " << baseMessage.sent.sec << "," << baseMessage.sent.usec << ", recv: " << baseMessage.received.sec << "," << baseMessage.received.usec << "\n";
|
// LOG(DEBUG) << "onMessageReceived: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " <<
|
||||||
|
//baseMessage.refersTo << ", sent: " << baseMessage.sent.sec << "," << baseMessage.sent.usec << ", recv: " << baseMessage.received.sec << "," <<
|
||||||
|
//baseMessage.received.usec << "\n";
|
||||||
if (baseMessage.type == message_type::kTime)
|
if (baseMessage.type == message_type::kTime)
|
||||||
{
|
{
|
||||||
auto timeMsg = make_shared<msg::Time>();
|
auto timeMsg = make_shared<msg::Time>();
|
||||||
timeMsg->deserialize(baseMessage, buffer);
|
timeMsg->deserialize(baseMessage, buffer);
|
||||||
timeMsg->refersTo = timeMsg->id;
|
timeMsg->refersTo = timeMsg->id;
|
||||||
timeMsg->latency = timeMsg->received - timeMsg->sent;
|
timeMsg->latency = timeMsg->received - timeMsg->sent;
|
||||||
// LOG(INFO) << "Latency sec: " << timeMsg.latency.sec << ", usec: " << timeMsg.latency.usec << ", refers to: " << timeMsg.refersTo << "\n";
|
// LOG(INFO) << "Latency sec: " << timeMsg.latency.sec << ", usec: " << timeMsg.latency.usec << ", refers to: " << timeMsg.refersTo <<
|
||||||
|
//"\n";
|
||||||
streamSession->sendAsync(timeMsg);
|
streamSession->sendAsync(timeMsg);
|
||||||
|
|
||||||
// refresh streamSession state
|
// refresh streamSession state
|
||||||
|
@ -533,7 +608,7 @@ void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::Ba
|
||||||
<< ", Protocol version: " << helloMsg.getProtocolVersion() << "\n";
|
<< ", Protocol version: " << helloMsg.getProtocolVersion() << "\n";
|
||||||
|
|
||||||
LOG(DEBUG) << "request kServerSettings: " << streamSession->clientId << "\n";
|
LOG(DEBUG) << "request kServerSettings: " << streamSession->clientId << "\n";
|
||||||
// std::lock_guard<std::mutex> mlock(mutex_);
|
// std::lock_guard<std::mutex> mlock(mutex_);
|
||||||
bool newGroup(false);
|
bool newGroup(false);
|
||||||
GroupPtr group = Config::instance().getGroupFromClient(streamSession->clientId);
|
GroupPtr group = Config::instance().getGroupFromClient(streamSession->clientId);
|
||||||
if (group == nullptr)
|
if (group == nullptr)
|
||||||
|
@ -583,7 +658,20 @@ void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::Ba
|
||||||
|
|
||||||
if (newGroup)
|
if (newGroup)
|
||||||
{
|
{
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025796,"usec":714671},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"},{"clients":[{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025798,"usec":728305},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"c5da8f7a-f377-1e51-8266-c5cc61099b71","muted":false,"name":"","stream_id":"stream 1"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
/// Notification:
|
||||||
|
/// {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123
|
||||||
|
/// 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025796,"usec":714671},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream
|
||||||
|
/// 2"},{"clients":[{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025798,"usec":728305},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"c5da8f7a-f377-1e51-8266-c5cc61099b71","muted":false,"name":"","stream_id":"stream
|
||||||
|
/// 1"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3
|
||||||
|
/// Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream
|
||||||
|
/// 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream
|
||||||
|
/// 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"buffer_ms":"20","codec":"flac","name":"stream
|
||||||
|
/// 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
json server = Config::instance().getServerStatus(streamManager_->toJson());
|
json server = Config::instance().getServerStatus(streamManager_->toJson());
|
||||||
json notification = jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server)).to_json();
|
json notification = jsonrpcpp::Notification("Server.OnUpdate", jsonrpcpp::Parameter("server", server)).to_json();
|
||||||
controlServer_->send(notification.dump());
|
controlServer_->send(notification.dump());
|
||||||
|
@ -591,13 +679,16 @@ void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::Ba
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/// Notification: {"jsonrpc":"2.0","method":"Client.OnConnect","params":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025524,"usec":876332},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},"id":"00:21:6a:7d:74:fc"}}
|
/// Notification:
|
||||||
|
/// {"jsonrpc":"2.0","method":"Client.OnConnect","params":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux
|
||||||
|
/// Mint 17.3
|
||||||
|
/// Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025524,"usec":876332},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},"id":"00:21:6a:7d:74:fc"}}
|
||||||
json notification = jsonrpcpp::Notification("Client.OnConnect", jsonrpcpp::Parameter("id", client->id, "client", client->toJson())).to_json();
|
json notification = jsonrpcpp::Notification("Client.OnConnect", jsonrpcpp::Parameter("id", client->id, "client", client->toJson())).to_json();
|
||||||
controlServer_->send(notification.dump());
|
controlServer_->send(notification.dump());
|
||||||
////cout << "Notification: " << notification.dump() << "\n";
|
////cout << "Notification: " << notification.dump() << "\n";
|
||||||
}
|
}
|
||||||
// cout << Config::instance().getServerStatus(streamManager_->toJson()).dump(4) << "\n";
|
// cout << Config::instance().getServerStatus(streamManager_->toJson()).dump(4) << "\n";
|
||||||
// cout << group->toJson().dump(4) << "\n";
|
// cout << group->toJson().dump(4) << "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,7 +697,7 @@ void StreamServer::onMessageReceived(StreamSession* streamSession, const msg::Ba
|
||||||
session_ptr StreamServer::getStreamSession(StreamSession* streamSession) const
|
session_ptr StreamServer::getStreamSession(StreamSession* streamSession) const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
||||||
for (auto session: sessions_)
|
for (auto session : sessions_)
|
||||||
{
|
{
|
||||||
if (session.get() == streamSession)
|
if (session.get() == streamSession)
|
||||||
return session;
|
return session;
|
||||||
|
@ -617,9 +708,9 @@ session_ptr StreamServer::getStreamSession(StreamSession* streamSession) const
|
||||||
|
|
||||||
session_ptr StreamServer::getStreamSession(const std::string& clientId) const
|
session_ptr StreamServer::getStreamSession(const std::string& clientId) const
|
||||||
{
|
{
|
||||||
// LOG(INFO) << "getStreamSession: " << mac << "\n";
|
// LOG(INFO) << "getStreamSession: " << mac << "\n";
|
||||||
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
||||||
for (auto session: sessions_)
|
for (auto session : sessions_)
|
||||||
{
|
{
|
||||||
if (session->clientId == clientId)
|
if (session->clientId == clientId)
|
||||||
return session;
|
return session;
|
||||||
|
@ -681,8 +772,8 @@ void StreamServer::start()
|
||||||
controlServer_->start();
|
controlServer_->start();
|
||||||
|
|
||||||
streamManager_.reset(new StreamManager(this, settings_.sampleFormat, settings_.codec, settings_.streamReadMs));
|
streamManager_.reset(new StreamManager(this, settings_.sampleFormat, settings_.codec, settings_.streamReadMs));
|
||||||
// throw SnapException("xxx");
|
// throw SnapException("xxx");
|
||||||
for (const auto& streamUri: settings_.pcmStreams)
|
for (const auto& streamUri : settings_.pcmStreams)
|
||||||
{
|
{
|
||||||
PcmStreamPtr stream = streamManager_->addStream(streamUri);
|
PcmStreamPtr stream = streamManager_->addStream(streamUri);
|
||||||
if (stream)
|
if (stream)
|
||||||
|
@ -741,7 +832,7 @@ void StreamServer::stop()
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
std::lock_guard<std::recursive_mutex> mlock(sessionsMutex_);
|
||||||
for (auto session: sessions_)//it = sessions_.begin(); it != sessions_.end(); ++it)
|
for (auto session : sessions_) // it = sessions_.begin(); it != sessions_.end(); ++it)
|
||||||
{
|
{
|
||||||
if (session)
|
if (session)
|
||||||
{
|
{
|
||||||
|
@ -769,4 +860,3 @@ void StreamServer::stop()
|
||||||
acceptor_v6_ = nullptr;
|
acceptor_v6_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,22 +20,22 @@
|
||||||
#define STREAM_SERVER_H
|
#define STREAM_SERVER_H
|
||||||
|
|
||||||
#include <asio.hpp>
|
#include <asio.hpp>
|
||||||
#include <vector>
|
|
||||||
#include <thread>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <mutex>
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "jsonrpcpp.hpp"
|
|
||||||
#include "streamSession.h"
|
|
||||||
#include "streamreader/streamManager.h"
|
|
||||||
#include "common/queue.h"
|
#include "common/queue.h"
|
||||||
#include "common/sampleFormat.h"
|
#include "common/sampleFormat.h"
|
||||||
#include "message/message.h"
|
|
||||||
#include "message/codecHeader.h"
|
|
||||||
#include "message/serverSettings.h"
|
|
||||||
#include "controlServer.h"
|
#include "controlServer.h"
|
||||||
|
#include "jsonrpcpp.hpp"
|
||||||
|
#include "message/codecHeader.h"
|
||||||
|
#include "message/message.h"
|
||||||
|
#include "message/serverSettings.h"
|
||||||
|
#include "streamSession.h"
|
||||||
|
#include "streamreader/streamManager.h"
|
||||||
|
|
||||||
|
|
||||||
using asio::ip::tcp;
|
using asio::ip::tcp;
|
||||||
|
@ -44,14 +44,8 @@ typedef std::shared_ptr<StreamSession> session_ptr;
|
||||||
|
|
||||||
struct StreamServerSettings
|
struct StreamServerSettings
|
||||||
{
|
{
|
||||||
StreamServerSettings() :
|
StreamServerSettings()
|
||||||
port(1704),
|
: port(1704), controlPort(1705), codec("flac"), bufferMs(1000), sampleFormat("48000:16:2"), streamReadMs(20), sendAudioToMutedClients(false)
|
||||||
controlPort(1705),
|
|
||||||
codec("flac"),
|
|
||||||
bufferMs(1000),
|
|
||||||
sampleFormat("48000:16:2"),
|
|
||||||
streamReadMs(20),
|
|
||||||
sendAudioToMutedClients(false)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
size_t port;
|
size_t port;
|
||||||
|
@ -82,7 +76,7 @@ public:
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
/// Send a message to all connceted clients
|
/// Send a message to all connceted clients
|
||||||
// void send(const msg::BaseMessage* message);
|
// void send(const msg::BaseMessage* message);
|
||||||
|
|
||||||
/// Clients call this when they receive a message. Implementation of MessageReceiver::onMessageReceived
|
/// Clients call this when they receive a message. Implementation of MessageReceiver::onMessageReceived
|
||||||
virtual void onMessageReceived(StreamSession* connection, const msg::BaseMessage& baseMessage, char* buffer);
|
virtual void onMessageReceived(StreamSession* connection, const msg::BaseMessage& baseMessage, char* buffer);
|
||||||
|
@ -118,5 +112,3 @@ private:
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,17 +18,17 @@
|
||||||
|
|
||||||
#include "streamSession.h"
|
#include "streamSession.h"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <mutex>
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
#include "message/pcmChunk.h"
|
#include "message/pcmChunk.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
StreamSession::StreamSession(MessageReceiver* receiver, std::shared_ptr<tcp::socket> socket) :
|
StreamSession::StreamSession(MessageReceiver* receiver, std::shared_ptr<tcp::socket> socket)
|
||||||
active_(false), readerThread_(nullptr), writerThread_(nullptr), messageReceiver_(receiver), pcmStream_(nullptr)
|
: active_(false), readerThread_(nullptr), writerThread_(nullptr), messageReceiver_(receiver), pcmStream_(nullptr)
|
||||||
{
|
{
|
||||||
socket_ = socket;
|
socket_ = socket;
|
||||||
}
|
}
|
||||||
|
@ -80,9 +80,11 @@ void StreamSession::stop()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> socketLock(socketMutex_);
|
std::lock_guard<std::mutex> socketLock(socketMutex_);
|
||||||
socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
socket_->shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
||||||
if (ec) LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n";
|
if (ec)
|
||||||
|
LOG(ERROR) << "Error in socket shutdown: " << ec.message() << "\n";
|
||||||
socket_->close(ec);
|
socket_->close(ec);
|
||||||
if (ec) LOG(ERROR) << "Error in socket close: " << ec.message() << "\n";
|
if (ec)
|
||||||
|
LOG(ERROR) << "Error in socket close: " << ec.message() << "\n";
|
||||||
}
|
}
|
||||||
if (readerThread_ && readerThread_->joinable())
|
if (readerThread_ && readerThread_->joinable())
|
||||||
{
|
{
|
||||||
|
@ -96,7 +98,7 @@ void StreamSession::stop()
|
||||||
writerThread_->join();
|
writerThread_->join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(...)
|
catch (...)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,8 +115,7 @@ void StreamSession::socketRead(void* _to, size_t _bytes)
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
read += socket_->read_some(asio::buffer((char*)_to + read, _bytes - read));
|
read += socket_->read_some(asio::buffer((char*)_to + read, _bytes - read));
|
||||||
}
|
} while (active_ && (read < _bytes));
|
||||||
while (active_ && (read < _bytes));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,8 +124,8 @@ void StreamSession::sendAsync(const msg::message_ptr& message, bool sendNow)
|
||||||
if (!message)
|
if (!message)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//the writer will take care about old messages
|
// the writer will take care about old messages
|
||||||
while (messages_.size() > 2000)// chunk->getDuration() > 10000)
|
while (messages_.size() > 2000) // chunk->getDuration() > 10000)
|
||||||
messages_.pop();
|
messages_.pop();
|
||||||
|
|
||||||
if (sendNow)
|
if (sendNow)
|
||||||
|
@ -148,8 +149,8 @@ void StreamSession::setBufferMs(size_t bufferMs)
|
||||||
|
|
||||||
bool StreamSession::send(const msg::message_ptr& message) const
|
bool StreamSession::send(const msg::message_ptr& message) const
|
||||||
{
|
{
|
||||||
//TODO on exception: set active = false
|
// TODO on exception: set active = false
|
||||||
// LOG(INFO) << "send: " << message->type << ", size: " << message->getSize() << ", id: " << message->id << ", refers: " << message->refersTo << "\n";
|
// LOG(INFO) << "send: " << message->type << ", size: " << message->getSize() << ", id: " << message->id << ", refers: " << message->refersTo << "\n";
|
||||||
std::lock_guard<std::mutex> socketLock(socketMutex_);
|
std::lock_guard<std::mutex> socketLock(socketMutex_);
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> activeLock(activeMutex_);
|
std::lock_guard<std::mutex> activeLock(activeMutex_);
|
||||||
|
@ -162,7 +163,7 @@ bool StreamSession::send(const msg::message_ptr& message) const
|
||||||
message->sent = t;
|
message->sent = t;
|
||||||
message->serialize(stream);
|
message->serialize(stream);
|
||||||
asio::write(*socket_.get(), streambuf);
|
asio::write(*socket_.get(), streambuf);
|
||||||
// LOG(INFO) << "done: " << message->type << ", size: " << message->size << ", id: " << message->id << ", refers: " << message->refersTo << "\n";
|
// LOG(INFO) << "done: " << message->type << ", size: " << message->size << ", id: " << message->id << ", refers: " << message->refersTo << "\n";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,13 +189,14 @@ void StreamSession::getNextMessage()
|
||||||
throw std::runtime_error(ss.str().c_str());
|
throw std::runtime_error(ss.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// LOG(INFO) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " << baseMessage.refersTo << "\n";
|
// LOG(INFO) << "getNextMessage: " << baseMessage.type << ", size: " << baseMessage.size << ", id: " << baseMessage.id << ", refers: " <<
|
||||||
|
//baseMessage.refersTo << "\n";
|
||||||
if (baseMessage.size > buffer.size())
|
if (baseMessage.size > buffer.size())
|
||||||
buffer.resize(baseMessage.size);
|
buffer.resize(baseMessage.size);
|
||||||
// {
|
// {
|
||||||
// std::lock_guard<std::mutex> socketLock(socketMutex_);
|
// std::lock_guard<std::mutex> socketLock(socketMutex_);
|
||||||
socketRead(&buffer[0], baseMessage.size);
|
socketRead(&buffer[0], baseMessage.size);
|
||||||
// }
|
// }
|
||||||
tv t;
|
tv t;
|
||||||
baseMessage.received = t;
|
baseMessage.received = t;
|
||||||
|
|
||||||
|
@ -242,7 +244,7 @@ void StreamSession::writer()
|
||||||
size_t age = 0;
|
size_t age = 0;
|
||||||
if (now > wireChunk->start())
|
if (now > wireChunk->start())
|
||||||
age = std::chrono::duration_cast<chronos::msec>(now - wireChunk->start()).count();
|
age = std::chrono::duration_cast<chronos::msec>(now - wireChunk->start()).count();
|
||||||
//LOG(DEBUG) << "PCM chunk. Age: " << age << ", buffer: " << bufferMs_ << ", age > buffer: " << (age > bufferMs_) << "\n";
|
// LOG(DEBUG) << "PCM chunk. Age: " << age << ", buffer: " << bufferMs_ << ", age > buffer: " << (age > bufferMs_) << "\n";
|
||||||
if (age > bufferMs_)
|
if (age > bufferMs_)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -259,5 +261,3 @@ void StreamSession::writer()
|
||||||
if (active_ && (messageReceiver_ != NULL))
|
if (active_ && (messageReceiver_ != NULL))
|
||||||
messageReceiver_->onDisconnect(this);
|
messageReceiver_->onDisconnect(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,17 +19,17 @@
|
||||||
#ifndef STREAM_SESSION_H
|
#ifndef STREAM_SESSION_H
|
||||||
#define STREAM_SESSION_H
|
#define STREAM_SESSION_H
|
||||||
|
|
||||||
|
#include "common/queue.h"
|
||||||
|
#include "message/message.h"
|
||||||
|
#include "streamreader/streamManager.h"
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
|
||||||
#include <memory>
|
|
||||||
#include <asio.hpp>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <set>
|
|
||||||
#include <mutex>
|
|
||||||
#include "message/message.h"
|
|
||||||
#include "common/queue.h"
|
|
||||||
#include "streamreader/streamManager.h"
|
|
||||||
|
|
||||||
|
|
||||||
using asio::ip::tcp;
|
using asio::ip::tcp;
|
||||||
|
@ -104,9 +104,4 @@ protected:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,11 +17,11 @@
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include "airplayStream.h"
|
#include "airplayStream.h"
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "common/utils/string_utils.h"
|
|
||||||
#include "common/utils.h"
|
|
||||||
#include "base64.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "base64.h"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
#include "common/utils.h"
|
||||||
|
#include "common/utils/string_utils.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ static string hex2str(string input)
|
||||||
typedef unsigned char byte;
|
typedef unsigned char byte;
|
||||||
unsigned long x = strtoul(input.c_str(), 0, 16);
|
unsigned long x = strtoul(input.c_str(), 0, 16);
|
||||||
byte a[] = {byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x), 0};
|
byte a[] = {byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x), 0};
|
||||||
return string((char *)a);
|
return string((char*)a);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -46,7 +46,7 @@ AirplayStream::AirplayStream(PcmListener* pcmListener, const StreamUri& uri) : P
|
||||||
logStderr_ = true;
|
logStderr_ = true;
|
||||||
|
|
||||||
pipePath_ = "/tmp/shairmeta." + cpt::to_string(getpid());
|
pipePath_ = "/tmp/shairmeta." + cpt::to_string(getpid());
|
||||||
//cout << "Pipe [" << pipePath_ << "]\n";
|
// cout << "Pipe [" << pipePath_ << "]\n";
|
||||||
|
|
||||||
// XXX: Check if pipe exists, delete or throw error
|
// XXX: Check if pipe exists, delete or throw error
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ int AirplayStream::parse(string line)
|
||||||
{
|
{
|
||||||
enum XML_Status result;
|
enum XML_Status result;
|
||||||
|
|
||||||
if((result = XML_Parse(parser_, line.c_str(), line.length(), false)) == XML_STATUS_ERROR)
|
if ((result = XML_Parse(parser_, line.c_str(), line.length(), false)) == XML_STATUS_ERROR)
|
||||||
{
|
{
|
||||||
XML_ParserFree(parser_);
|
XML_ParserFree(parser_);
|
||||||
createParser();
|
createParser();
|
||||||
|
@ -100,18 +100,22 @@ void AirplayStream::createParser()
|
||||||
void AirplayStream::push()
|
void AirplayStream::push()
|
||||||
{
|
{
|
||||||
string data = entry_->data;
|
string data = entry_->data;
|
||||||
if(entry_->isBase64 && entry_->length > 0)
|
if (entry_->isBase64 && entry_->length > 0)
|
||||||
data = base64_decode(data);
|
data = base64_decode(data);
|
||||||
|
|
||||||
if(entry_->type == "ssnc" && entry_->code == "mdst")
|
if (entry_->type == "ssnc" && entry_->code == "mdst")
|
||||||
jtag_ = json();
|
jtag_ = json();
|
||||||
|
|
||||||
if(entry_->code == "asal") jtag_["ALBUM"] = data;
|
if (entry_->code == "asal")
|
||||||
if(entry_->code == "asar") jtag_["ARTIST"] = data;
|
jtag_["ALBUM"] = data;
|
||||||
if(entry_->code == "minm") jtag_["TITLE"] = data;
|
if (entry_->code == "asar")
|
||||||
|
jtag_["ARTIST"] = data;
|
||||||
|
if (entry_->code == "minm")
|
||||||
|
jtag_["TITLE"] = data;
|
||||||
|
|
||||||
if(entry_->type == "ssnc" && entry_->code == "mden"){
|
if (entry_->type == "ssnc" && entry_->code == "mden")
|
||||||
//LOG(INFO) << "metadata=" << jtag_.dump(4) << "\n";
|
{
|
||||||
|
// LOG(INFO) << "metadata=" << jtag_.dump(4) << "\n";
|
||||||
setMeta(jtag_);
|
setMeta(jtag_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,14 +127,16 @@ void AirplayStream::pipeReader()
|
||||||
createParser();
|
createParser();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
while(true)
|
while (true)
|
||||||
{
|
{
|
||||||
ifstream pipe(pipePath_);
|
ifstream pipe(pipePath_);
|
||||||
|
|
||||||
if(pipe){
|
if (pipe)
|
||||||
|
{
|
||||||
string line;
|
string line;
|
||||||
|
|
||||||
while(getline(pipe, line)){
|
while (getline(pipe, line))
|
||||||
|
{
|
||||||
#ifdef HAS_EXPAT
|
#ifdef HAS_EXPAT
|
||||||
parse(line);
|
parse(line);
|
||||||
#endif
|
#endif
|
||||||
|
@ -181,51 +187,54 @@ void AirplayStream::onStderrMsg(const char* buffer, size_t n)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_EXPAT
|
#ifdef HAS_EXPAT
|
||||||
void XMLCALL AirplayStream::element_start(void *userdata, const char *element_name, const char **attr)
|
void XMLCALL AirplayStream::element_start(void* userdata, const char* element_name, const char** attr)
|
||||||
{
|
{
|
||||||
AirplayStream *self = (AirplayStream *)userdata;
|
AirplayStream* self = (AirplayStream*)userdata;
|
||||||
string name(element_name);
|
string name(element_name);
|
||||||
|
|
||||||
self->buf_.assign("");
|
self->buf_.assign("");
|
||||||
if(name == "item") self->entry_.reset(new TageEntry);
|
if (name == "item")
|
||||||
|
self->entry_.reset(new TageEntry);
|
||||||
|
|
||||||
for(int i = 0; attr[i]; i += 2){
|
for (int i = 0; attr[i]; i += 2)
|
||||||
|
{
|
||||||
string name(attr[i]);
|
string name(attr[i]);
|
||||||
string value(attr[i+1]);
|
string value(attr[i + 1]);
|
||||||
if(name == "encoding")
|
if (name == "encoding")
|
||||||
self->entry_->isBase64 = (value == "base64"); // Quick & dirty..
|
self->entry_->isBase64 = (value == "base64"); // Quick & dirty..
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLCALL AirplayStream::element_end(void *userdata, const char *element_name)
|
void XMLCALL AirplayStream::element_end(void* userdata, const char* element_name)
|
||||||
{
|
{
|
||||||
AirplayStream *self = (AirplayStream *)userdata;
|
AirplayStream* self = (AirplayStream*)userdata;
|
||||||
string name(element_name);
|
string name(element_name);
|
||||||
|
|
||||||
if(name == "code")
|
if (name == "code")
|
||||||
self->entry_->code.assign(hex2str(self->buf_));
|
self->entry_->code.assign(hex2str(self->buf_));
|
||||||
|
|
||||||
else if(name == "type")
|
else if (name == "type")
|
||||||
self->entry_->type.assign(hex2str(self->buf_));
|
self->entry_->type.assign(hex2str(self->buf_));
|
||||||
|
|
||||||
else if(name == "length")
|
else if (name == "length")
|
||||||
self->entry_->length = strtoul(self->buf_.c_str(), 0, 10);
|
self->entry_->length = strtoul(self->buf_.c_str(), 0, 10);
|
||||||
|
|
||||||
else if(name == "data")
|
else if (name == "data")
|
||||||
self->entry_->data = self->buf_;
|
self->entry_->data = self->buf_;
|
||||||
|
|
||||||
else if(name == "item")
|
else if (name == "item")
|
||||||
self->push();
|
self->push();
|
||||||
|
|
||||||
else if(name == "metatags") ;
|
else if (name == "metatags")
|
||||||
else cout << "Unknown tag <" << name << ">\n";
|
;
|
||||||
|
else
|
||||||
|
cout << "Unknown tag <" << name << ">\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLCALL AirplayStream::data(void *userdata, const char *content, int length)
|
void XMLCALL AirplayStream::data(void* userdata, const char* content, int length)
|
||||||
{
|
{
|
||||||
AirplayStream *self = (AirplayStream *)userdata;
|
AirplayStream* self = (AirplayStream*)userdata;
|
||||||
string value(content, (size_t)length);
|
string value(content, (size_t)length);
|
||||||
self->buf_.append(value);
|
self->buf_.append(value);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -32,7 +32,9 @@
|
||||||
class TageEntry
|
class TageEntry
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TageEntry(): isBase64(false), length(0) {}
|
TageEntry() : isBase64(false), length(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
std::string code;
|
std::string code;
|
||||||
std::string type;
|
std::string type;
|
||||||
|
@ -80,9 +82,9 @@ protected:
|
||||||
std::thread pipeReaderThread_;
|
std::thread pipeReaderThread_;
|
||||||
|
|
||||||
#ifdef HAS_EXPAT
|
#ifdef HAS_EXPAT
|
||||||
static void XMLCALL element_start(void *userdata, const char *element_name, const char **attr);
|
static void XMLCALL element_start(void* userdata, const char* element_name, const char** attr);
|
||||||
static void XMLCALL element_end(void *userdata, const char *element_name);
|
static void XMLCALL element_end(void* userdata, const char* element_name);
|
||||||
static void XMLCALL data(void *userdata, const char *content, int length);
|
static void XMLCALL data(void* userdata, const char* content, int length);
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,32 +26,35 @@
|
||||||
*/
|
*/
|
||||||
#include "base64.h"
|
#include "base64.h"
|
||||||
|
|
||||||
static const std::string base64_chars =
|
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
"0123456789+/";
|
"0123456789+/";
|
||||||
|
|
||||||
|
|
||||||
static inline bool is_base64(unsigned char c) {
|
static inline bool is_base64(unsigned char c)
|
||||||
|
{
|
||||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
|
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len)
|
||||||
|
{
|
||||||
std::string ret;
|
std::string ret;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int j = 0;
|
int j = 0;
|
||||||
unsigned char char_array_3[3];
|
unsigned char char_array_3[3];
|
||||||
unsigned char char_array_4[4];
|
unsigned char char_array_4[4];
|
||||||
|
|
||||||
while (in_len--) {
|
while (in_len--)
|
||||||
|
{
|
||||||
char_array_3[i++] = *(bytes_to_encode++);
|
char_array_3[i++] = *(bytes_to_encode++);
|
||||||
if (i == 3) {
|
if (i == 3)
|
||||||
|
{
|
||||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||||
|
|
||||||
for(i = 0; (i <4) ; i++)
|
for (i = 0; (i < 4); i++)
|
||||||
ret += base64_chars[char_array_4[i]];
|
ret += base64_chars[char_array_4[i]];
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +62,7 @@ std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_
|
||||||
|
|
||||||
if (i)
|
if (i)
|
||||||
{
|
{
|
||||||
for(j = i; j < 3; j++)
|
for (j = i; j < 3; j++)
|
||||||
char_array_3[j] = '\0';
|
char_array_3[j] = '\0';
|
||||||
|
|
||||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||||
|
@ -70,15 +73,14 @@ std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_
|
||||||
for (j = 0; (j < i + 1); j++)
|
for (j = 0; (j < i + 1); j++)
|
||||||
ret += base64_chars[char_array_4[j]];
|
ret += base64_chars[char_array_4[j]];
|
||||||
|
|
||||||
while((i++ < 3))
|
while ((i++ < 3))
|
||||||
ret += '=';
|
ret += '=';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
std::string base64_decode(std::string const& encoded_string) {
|
std::string base64_decode(std::string const& encoded_string)
|
||||||
|
{
|
||||||
int in_len = encoded_string.size();
|
int in_len = encoded_string.size();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int j = 0;
|
int j = 0;
|
||||||
|
@ -86,10 +88,13 @@ std::string base64_decode(std::string const& encoded_string) {
|
||||||
unsigned char char_array_4[4], char_array_3[3];
|
unsigned char char_array_4[4], char_array_3[3];
|
||||||
std::string ret;
|
std::string ret;
|
||||||
|
|
||||||
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
|
while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
|
||||||
char_array_4[i++] = encoded_string[in_]; in_++;
|
{
|
||||||
if (i ==4) {
|
char_array_4[i++] = encoded_string[in_];
|
||||||
for (i = 0; i <4; i++)
|
in_++;
|
||||||
|
if (i == 4)
|
||||||
|
{
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
||||||
|
|
||||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
|
@ -102,20 +107,21 @@ std::string base64_decode(std::string const& encoded_string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i) {
|
if (i)
|
||||||
for (j = i; j <4; j++)
|
{
|
||||||
|
for (j = i; j < 4; j++)
|
||||||
char_array_4[j] = 0;
|
char_array_4[j] = 0;
|
||||||
|
|
||||||
for (j = 0; j <4; j++)
|
for (j = 0; j < 4; j++)
|
||||||
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
||||||
|
|
||||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
for (j = 0; (j < i - 1); j++)
|
||||||
|
ret += char_array_3[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,24 +16,23 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
|
||||||
|
|
||||||
#include "fileStream.h"
|
|
||||||
#include "encoder/encoderFactory.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
|
#include "encoder/encoderFactory.h"
|
||||||
|
#include "fileStream.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FileStream::FileStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri)
|
FileStream::FileStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri)
|
||||||
{
|
{
|
||||||
ifs.open(uri_.path.c_str(), std::ifstream::in|std::ifstream::binary);
|
ifs.open(uri_.path.c_str(), std::ifstream::in | std::ifstream::binary);
|
||||||
if (!ifs.good())
|
if (!ifs.good())
|
||||||
{
|
{
|
||||||
LOG(ERROR) << "failed to open PCM file: \"" + uri_.path + "\"\n";
|
LOG(ERROR) << "failed to open PCM file: \"" + uri_.path + "\"\n";
|
||||||
|
@ -53,9 +52,9 @@ void FileStream::worker()
|
||||||
timeval tvChunk;
|
timeval tvChunk;
|
||||||
std::unique_ptr<msg::PcmChunk> chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_));
|
std::unique_ptr<msg::PcmChunk> chunk(new msg::PcmChunk(sampleFormat_, pcmReadMs_));
|
||||||
|
|
||||||
ifs.seekg (0, ifs.end);
|
ifs.seekg(0, ifs.end);
|
||||||
size_t length = ifs.tellg();
|
size_t length = ifs.tellg();
|
||||||
ifs.seekg (0, ifs.beg);
|
ifs.seekg(0, ifs.beg);
|
||||||
|
|
||||||
setState(kPlaying);
|
setState(kPlaying);
|
||||||
|
|
||||||
|
@ -78,20 +77,21 @@ void FileStream::worker()
|
||||||
if (left < toRead)
|
if (left < toRead)
|
||||||
{
|
{
|
||||||
ifs.read(chunk->payload, left);
|
ifs.read(chunk->payload, left);
|
||||||
ifs.seekg (0, ifs.beg);
|
ifs.seekg(0, ifs.beg);
|
||||||
count = left;
|
count = left;
|
||||||
}
|
}
|
||||||
ifs.read(chunk->payload + count, toRead - count);
|
ifs.read(chunk->payload + count, toRead - count);
|
||||||
|
|
||||||
encoder_->encode(chunk.get());
|
encoder_->encode(chunk.get());
|
||||||
if (!active_) break;
|
if (!active_)
|
||||||
|
break;
|
||||||
nextTick += pcmReadMs_;
|
nextTick += pcmReadMs_;
|
||||||
chronos::addUs(tvChunk, pcmReadMs_ * 1000);
|
chronos::addUs(tvChunk, pcmReadMs_ * 1000);
|
||||||
long currentTick = chronos::getTickCount();
|
long currentTick = chronos::getTickCount();
|
||||||
|
|
||||||
if (nextTick >= currentTick)
|
if (nextTick >= currentTick)
|
||||||
{
|
{
|
||||||
// LOG(INFO) << "sleep: " << nextTick - currentTick << "\n";
|
// LOG(INFO) << "sleep: " << nextTick - currentTick << "\n";
|
||||||
if (!sleep(nextTick - currentTick))
|
if (!sleep(nextTick - currentTick))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ void FileStream::worker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
LOG(ERROR) << "(FileStream) Exception: " << e.what() << std::endl;
|
LOG(ERROR) << "(FileStream) Exception: " << e.what() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,23 +16,22 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
|
||||||
|
|
||||||
#include "encoder/encoderFactory.h"
|
#include "aixlog.hpp"
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
#include "common/strCompat.h"
|
#include "common/strCompat.h"
|
||||||
|
#include "encoder/encoderFactory.h"
|
||||||
#include "pcmStream.h"
|
#include "pcmStream.h"
|
||||||
#include "aixlog.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PcmStream::PcmStream(PcmListener* pcmListener, const StreamUri& uri) :
|
PcmStream::PcmStream(PcmListener* pcmListener, const StreamUri& uri) : active_(false), pcmListener_(pcmListener), uri_(uri), pcmReadMs_(20), state_(kIdle)
|
||||||
active_(false), pcmListener_(pcmListener), uri_(uri), pcmReadMs_(20), state_(kIdle)
|
|
||||||
{
|
{
|
||||||
EncoderFactory encoderFactory;
|
EncoderFactory encoderFactory;
|
||||||
if (uri_.query.find("codec") == uri_.query.end())
|
if (uri_.query.find("codec") == uri_.query.end())
|
||||||
|
@ -56,8 +55,8 @@ PcmStream::PcmStream(PcmListener* pcmListener, const StreamUri& uri) :
|
||||||
else
|
else
|
||||||
dryoutMs_ = 2000;
|
dryoutMs_ = 2000;
|
||||||
|
|
||||||
//meta_.reset(new msg::StreamTags());
|
// meta_.reset(new msg::StreamTags());
|
||||||
//meta_->msg["stream"] = name_;
|
// meta_->msg["stream"] = name_;
|
||||||
setMeta(json());
|
setMeta(json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +146,7 @@ void PcmStream::setState(const ReaderState& newState)
|
||||||
|
|
||||||
void PcmStream::onChunkEncoded(const Encoder* encoder, msg::PcmChunk* chunk, double duration)
|
void PcmStream::onChunkEncoded(const Encoder* encoder, msg::PcmChunk* chunk, double duration)
|
||||||
{
|
{
|
||||||
// LOG(INFO) << "onChunkEncoded: " << duration << " us\n";
|
// LOG(INFO) << "onChunkEncoded: " << duration << " us\n";
|
||||||
if (duration <= 0)
|
if (duration <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -170,12 +169,10 @@ json PcmStream::toJson() const
|
||||||
state = "disabled";
|
state = "disabled";
|
||||||
|
|
||||||
json j = {
|
json j = {
|
||||||
{"uri", uri_.toJson()},
|
{"uri", uri_.toJson()}, {"id", getId()}, {"status", state},
|
||||||
{"id", getId()},
|
|
||||||
{"status", state},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if(meta_)
|
if (meta_)
|
||||||
j["meta"] = meta_->msg;
|
j["meta"] = meta_->msg;
|
||||||
|
|
||||||
return j;
|
return j;
|
||||||
|
@ -196,4 +193,3 @@ void PcmStream::setMeta(json jtag)
|
||||||
if (pcmListener_)
|
if (pcmListener_)
|
||||||
pcmListener_->onMetaChanged(this);
|
pcmListener_->onMetaChanged(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,18 +19,18 @@
|
||||||
#ifndef PCM_STREAM_H
|
#ifndef PCM_STREAM_H
|
||||||
#define PCM_STREAM_H
|
#define PCM_STREAM_H
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
#include <atomic>
|
|
||||||
#include <string>
|
|
||||||
#include <mutex>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <map>
|
|
||||||
#include "streamUri.h"
|
|
||||||
#include "encoder/encoder.h"
|
|
||||||
#include "common/sampleFormat.h"
|
|
||||||
#include "common/json.hpp"
|
#include "common/json.hpp"
|
||||||
|
#include "common/sampleFormat.h"
|
||||||
|
#include "encoder/encoder.h"
|
||||||
#include "message/codecHeader.h"
|
#include "message/codecHeader.h"
|
||||||
#include "message/streamTags.h"
|
#include "message/streamTags.h"
|
||||||
|
#include "streamUri.h"
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
|
||||||
class PcmStream;
|
class PcmStream;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,24 +16,23 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <cerrno>
|
|
||||||
|
|
||||||
#include "pipeStream.h"
|
#include "aixlog.hpp"
|
||||||
#include "encoder/encoderFactory.h"
|
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
#include "common/strCompat.h"
|
#include "common/strCompat.h"
|
||||||
#include "aixlog.hpp"
|
#include "encoder/encoderFactory.h"
|
||||||
|
#include "pipeStream.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PipeStream::PipeStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), fd_(-1)
|
PipeStream::PipeStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), fd_(-1)
|
||||||
{
|
{
|
||||||
umask(0);
|
umask(0);
|
||||||
|
@ -73,7 +72,7 @@ void PipeStream::worker()
|
||||||
tvEncodedChunk_ = tvChunk;
|
tvEncodedChunk_ = tvChunk;
|
||||||
long nextTick = chronos::getTickCount();
|
long nextTick = chronos::getTickCount();
|
||||||
int idleBytes = 0;
|
int idleBytes = 0;
|
||||||
int maxIdleBytes = sampleFormat_.rate*sampleFormat_.frameSize*dryoutMs_/1000;
|
int maxIdleBytes = sampleFormat_.rate * sampleFormat_.frameSize * dryoutMs_ / 1000;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (fd_ == -1)
|
if (fd_ == -1)
|
||||||
|
@ -108,15 +107,16 @@ void PipeStream::worker()
|
||||||
len += count;
|
len += count;
|
||||||
idleBytes = 0;
|
idleBytes = 0;
|
||||||
}
|
}
|
||||||
}
|
} while ((len < toRead) && active_);
|
||||||
while ((len < toRead) && active_);
|
|
||||||
|
|
||||||
if (!active_) break;
|
if (!active_)
|
||||||
|
break;
|
||||||
|
|
||||||
/// TODO: use less raw pointers, make this encoding more transparent
|
/// TODO: use less raw pointers, make this encoding more transparent
|
||||||
encoder_->encode(chunk.get());
|
encoder_->encode(chunk.get());
|
||||||
|
|
||||||
if (!active_) break;
|
if (!active_)
|
||||||
|
break;
|
||||||
|
|
||||||
nextTick += pcmReadMs_;
|
nextTick += pcmReadMs_;
|
||||||
chronos::addUs(tvChunk, pcmReadMs_ * 1000);
|
chronos::addUs(tvChunk, pcmReadMs_ * 1000);
|
||||||
|
@ -139,7 +139,7 @@ void PipeStream::worker()
|
||||||
lastException = "";
|
lastException = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
if (lastException != e.what())
|
if (lastException != e.what())
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,21 +17,20 @@
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include "processStream.h"
|
#include "processStream.h"
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "common/utils/string_utils.h"
|
|
||||||
#include "common/utils.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
#include "common/utils.h"
|
||||||
|
#include "common/utils/string_utils.h"
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ProcessStream::ProcessStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), path_(""), process_(nullptr)
|
ProcessStream::ProcessStream(PcmListener* pcmListener, const StreamUri& uri) : PcmStream(pcmListener, uri), path_(""), process_(nullptr)
|
||||||
{
|
{
|
||||||
params_ = uri_.getQuery("params");
|
params_ = uri_.getQuery("params");
|
||||||
|
@ -72,7 +71,7 @@ std::string ProcessStream::findExe(const std::string& filename)
|
||||||
char buff[PATH_MAX];
|
char buff[PATH_MAX];
|
||||||
char szTmp[32];
|
char szTmp[32];
|
||||||
sprintf(szTmp, "/proc/%d/exe", getpid());
|
sprintf(szTmp, "/proc/%d/exe", getpid());
|
||||||
ssize_t len = readlink(szTmp, buff, sizeof(buff)-1);
|
ssize_t len = readlink(szTmp, buff, sizeof(buff) - 1);
|
||||||
if (len != -1)
|
if (len != -1)
|
||||||
{
|
{
|
||||||
buff[len] = '\0';
|
buff[len] = '\0';
|
||||||
|
@ -134,7 +133,7 @@ void ProcessStream::stderrReader()
|
||||||
auto buffer = std::unique_ptr<char[]>(new char[buffer_size]);
|
auto buffer = std::unique_ptr<char[]>(new char[buffer_size]);
|
||||||
ssize_t n;
|
ssize_t n;
|
||||||
stringstream message;
|
stringstream message;
|
||||||
while (active_ && (n=read(process_->getStderr(), buffer.get(), buffer_size)) > 0)
|
while (active_ && (n = read(process_->getStderr(), buffer.get(), buffer_size)) > 0)
|
||||||
onStderrMsg(buffer.get(), n);
|
onStderrMsg(buffer.get(), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +158,7 @@ void ProcessStream::worker()
|
||||||
tvEncodedChunk_ = tvChunk;
|
tvEncodedChunk_ = tvChunk;
|
||||||
long nextTick = chronos::getTickCount();
|
long nextTick = chronos::getTickCount();
|
||||||
int idleBytes = 0;
|
int idleBytes = 0;
|
||||||
int maxIdleBytes = sampleFormat_.rate*sampleFormat_.frameSize*dryoutMs_/1000;
|
int maxIdleBytes = sampleFormat_.rate * sampleFormat_.frameSize * dryoutMs_ / 1000;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (active_)
|
while (active_)
|
||||||
|
@ -191,14 +190,15 @@ void ProcessStream::worker()
|
||||||
len += count;
|
len += count;
|
||||||
idleBytes = 0;
|
idleBytes = 0;
|
||||||
}
|
}
|
||||||
}
|
} while ((len < toRead) && active_);
|
||||||
while ((len < toRead) && active_);
|
|
||||||
|
|
||||||
if (!active_) break;
|
if (!active_)
|
||||||
|
break;
|
||||||
|
|
||||||
encoder_->encode(chunk.get());
|
encoder_->encode(chunk.get());
|
||||||
|
|
||||||
if (!active_) break;
|
if (!active_)
|
||||||
|
break;
|
||||||
|
|
||||||
nextTick += pcmReadMs_;
|
nextTick += pcmReadMs_;
|
||||||
chronos::addUs(tvChunk, pcmReadMs_ * 1000);
|
chronos::addUs(tvChunk, pcmReadMs_ * 1000);
|
||||||
|
@ -221,7 +221,7 @@ void ProcessStream::worker()
|
||||||
lastException = "";
|
lastException = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
if (lastException != e.what())
|
if (lastException != e.what())
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,19 +16,18 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <regex>
|
|
||||||
#include "spotifyStream.h"
|
#include "spotifyStream.h"
|
||||||
#include "common/snapException.h"
|
|
||||||
#include "common/utils/string_utils.h"
|
|
||||||
#include "common/utils.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
#include "common/utils.h"
|
||||||
|
#include "common/utils/string_utils.h"
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SpotifyStream::SpotifyStream(PcmListener* pcmListener, const StreamUri& uri) : ProcessStream(pcmListener, uri)
|
SpotifyStream::SpotifyStream(PcmListener* pcmListener, const StreamUri& uri) : ProcessStream(pcmListener, uri)
|
||||||
{
|
{
|
||||||
sampleFormat_ = SampleFormat("44100:16:2");
|
sampleFormat_ = SampleFormat("44100:16:2");
|
||||||
|
@ -60,7 +59,7 @@ SpotifyStream::SpotifyStream(PcmListener* pcmListener, const StreamUri& uri) : P
|
||||||
uri_.query["username"] = "xxx";
|
uri_.query["username"] = "xxx";
|
||||||
if (uri_.query.find("password") != uri_.query.end())
|
if (uri_.query.find("password") != uri_.query.end())
|
||||||
uri_.query["password"] = "xxx";
|
uri_.query["password"] = "xxx";
|
||||||
// LOG(INFO) << "params: " << params << "\n";
|
// LOG(INFO) << "params: " << params << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -119,9 +118,7 @@ void SpotifyStream::onStderrMsg(const char* buffer, size_t n)
|
||||||
watchdog_->trigger();
|
watchdog_->trigger();
|
||||||
string logmsg = utils::string::trim_copy(string(buffer, n));
|
string logmsg = utils::string::trim_copy(string(buffer, n));
|
||||||
|
|
||||||
if ((logmsg.find("allocated stream") == string::npos) &&
|
if ((logmsg.find("allocated stream") == string::npos) && (logmsg.find("Got channel") == string::npos) && (logmsg.find('\0') == string::npos) &&
|
||||||
(logmsg.find("Got channel") == string::npos) &&
|
|
||||||
(logmsg.find('\0') == string::npos) &&
|
|
||||||
(logmsg.size() > 4))
|
(logmsg.size() > 4))
|
||||||
{
|
{
|
||||||
LOG(INFO) << "(" << getName() << ") " << logmsg << "\n";
|
LOG(INFO) << "(" << getName() << ") " << logmsg << "\n";
|
||||||
|
@ -137,13 +134,11 @@ void SpotifyStream::onStderrMsg(const char* buffer, size_t n)
|
||||||
if (!libreelec_patched)
|
if (!libreelec_patched)
|
||||||
{
|
{
|
||||||
static regex re_nonpatched("Track \"(.*)\" loaded");
|
static regex re_nonpatched("Track \"(.*)\" loaded");
|
||||||
if(regex_search(logmsg, m, re_nonpatched))
|
if (regex_search(logmsg, m, re_nonpatched))
|
||||||
{
|
{
|
||||||
LOG(INFO) << "metadata: <" << m[1] << ">\n";
|
LOG(INFO) << "metadata: <" << m[1] << ">\n";
|
||||||
|
|
||||||
json jtag = {
|
json jtag = {{"TITLE", (string)m[1]}};
|
||||||
{"TITLE", (string)m[1]}
|
|
||||||
};
|
|
||||||
setMeta(jtag);
|
setMeta(jtag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +159,7 @@ void SpotifyStream::stderrReader()
|
||||||
{
|
{
|
||||||
watchdog_.reset(new Watchdog(this));
|
watchdog_.reset(new Watchdog(this));
|
||||||
/// 130min
|
/// 130min
|
||||||
watchdog_->start(130*60*1000);
|
watchdog_->start(130 * 60 * 1000);
|
||||||
ProcessStream::stderrReader();
|
ProcessStream::stderrReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,5 +170,3 @@ void SpotifyStream::onTimeout(const Watchdog* watchdog, size_t ms)
|
||||||
if (process_)
|
if (process_)
|
||||||
process_->kill();
|
process_->kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -28,7 +28,8 @@
|
||||||
* Implements EncoderListener to get the encoded data.
|
* Implements EncoderListener to get the encoded data.
|
||||||
* Data is passed to the PcmListener
|
* Data is passed to the PcmListener
|
||||||
* usage:
|
* usage:
|
||||||
* snapserver -s "spotify:///librespot?name=Spotify&username=<my username>&password=<my password>[&devicename=Snapcast][&bitrate=320][&volume=<volume in percent>][&cache=<cache dir>]"
|
* snapserver -s "spotify:///librespot?name=Spotify&username=<my username>&password=<my password>[&devicename=Snapcast][&bitrate=320][&volume=<volume in
|
||||||
|
* percent>][&cache=<cache dir>]"
|
||||||
*/
|
*/
|
||||||
class SpotifyStream : public ProcessStream, WatchdogListener
|
class SpotifyStream : public ProcessStream, WatchdogListener
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/***
|
/***
|
||||||
This file is part of snapcast
|
This file is part of snapcast
|
||||||
Copyright (C) 2014-2018 Johannes Pohl
|
Copyright (C) 2014-2019 Johannes Pohl
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,20 +18,21 @@
|
||||||
|
|
||||||
#include "streamManager.h"
|
#include "streamManager.h"
|
||||||
#include "airplayStream.h"
|
#include "airplayStream.h"
|
||||||
#include "spotifyStream.h"
|
|
||||||
#include "processStream.h"
|
|
||||||
#include "pipeStream.h"
|
|
||||||
#include "fileStream.h"
|
|
||||||
#include "common/utils.h"
|
|
||||||
#include "common/strCompat.h"
|
|
||||||
#include "aixlog.hpp"
|
#include "aixlog.hpp"
|
||||||
#include "common/snapException.h"
|
#include "common/snapException.h"
|
||||||
|
#include "common/strCompat.h"
|
||||||
|
#include "common/utils.h"
|
||||||
|
#include "fileStream.h"
|
||||||
|
#include "pipeStream.h"
|
||||||
|
#include "processStream.h"
|
||||||
|
#include "spotifyStream.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
StreamManager::StreamManager(PcmListener* pcmListener, const std::string& defaultSampleFormat, const std::string& defaultCodec, size_t defaultReadBufferMs) : pcmListener_(pcmListener), sampleFormat_(defaultSampleFormat), codec_(defaultCodec), readBufferMs_(defaultReadBufferMs)
|
StreamManager::StreamManager(PcmListener* pcmListener, const std::string& defaultSampleFormat, const std::string& defaultCodec, size_t defaultReadBufferMs)
|
||||||
|
: pcmListener_(pcmListener), sampleFormat_(defaultSampleFormat), codec_(defaultCodec), readBufferMs_(defaultReadBufferMs)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,11 +50,11 @@ PcmStreamPtr StreamManager::addStream(const std::string& uri)
|
||||||
if (streamUri.query.find("buffer_ms") == streamUri.query.end())
|
if (streamUri.query.find("buffer_ms") == streamUri.query.end())
|
||||||
streamUri.query["buffer_ms"] = cpt::to_string(readBufferMs_);
|
streamUri.query["buffer_ms"] = cpt::to_string(readBufferMs_);
|
||||||
|
|
||||||
// LOG(DEBUG) << "\nURI: " << streamUri.uri << "\nscheme: " << streamUri.scheme << "\nhost: "
|
// LOG(DEBUG) << "\nURI: " << streamUri.uri << "\nscheme: " << streamUri.scheme << "\nhost: "
|
||||||
// << streamUri.host << "\npath: " << streamUri.path << "\nfragment: " << streamUri.fragment << "\n";
|
// << streamUri.host << "\npath: " << streamUri.path << "\nfragment: " << streamUri.fragment << "\n";
|
||||||
|
|
||||||
// for (auto kv: streamUri.query)
|
// for (auto kv: streamUri.query)
|
||||||
// LOG(DEBUG) << "key: '" << kv.first << "' value: '" << kv.second << "'\n";
|
// LOG(DEBUG) << "key: '" << kv.first << "' value: '" << kv.second << "'\n";
|
||||||
PcmStreamPtr stream(nullptr);
|
PcmStreamPtr stream(nullptr);
|
||||||
|
|
||||||
if (streamUri.scheme == "pipe")
|
if (streamUri.scheme == "pipe")
|
||||||
|
@ -83,7 +84,7 @@ PcmStreamPtr StreamManager::addStream(const std::string& uri)
|
||||||
|
|
||||||
if (stream)
|
if (stream)
|
||||||
{
|
{
|
||||||
for (auto s: streams_)
|
for (auto s : streams_)
|
||||||
{
|
{
|
||||||
if (s->getName() == stream->getName())
|
if (s->getName() == stream->getName())
|
||||||
throw SnapException("Stream with name \"" + stream->getName() + "\" already exists");
|
throw SnapException("Stream with name \"" + stream->getName() + "\" already exists");
|
||||||
|
@ -112,7 +113,7 @@ const PcmStreamPtr StreamManager::getDefaultStream()
|
||||||
|
|
||||||
const PcmStreamPtr StreamManager::getStream(const std::string& id)
|
const PcmStreamPtr StreamManager::getStream(const std::string& id)
|
||||||
{
|
{
|
||||||
for (auto stream: streams_)
|
for (auto stream : streams_)
|
||||||
{
|
{
|
||||||
if (stream->getId() == id)
|
if (stream->getId() == id)
|
||||||
return stream;
|
return stream;
|
||||||
|
@ -123,14 +124,14 @@ const PcmStreamPtr StreamManager::getStream(const std::string& id)
|
||||||
|
|
||||||
void StreamManager::start()
|
void StreamManager::start()
|
||||||
{
|
{
|
||||||
for (auto stream: streams_)
|
for (auto stream : streams_)
|
||||||
stream->start();
|
stream->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamManager::stop()
|
void StreamManager::stop()
|
||||||
{
|
{
|
||||||
for (auto stream: streams_)
|
for (auto stream : streams_)
|
||||||
stream->stop();
|
stream->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,9 +139,7 @@ void StreamManager::stop()
|
||||||
json StreamManager::toJson() const
|
json StreamManager::toJson() const
|
||||||
{
|
{
|
||||||
json result = json::array();
|
json result = json::array();
|
||||||
for (auto stream: streams_)
|
for (auto stream : streams_)
|
||||||
result.push_back(stream->toJson());
|
result.push_back(stream->toJson());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#ifndef PCM_READER_FACTORY_H
|
#ifndef PCM_READER_FACTORY_H
|
||||||
#define PCM_READER_FACTORY_H
|
#define PCM_READER_FACTORY_H
|
||||||
|
|
||||||
|
#include "pcmStream.h"
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
|
||||||
#include "pcmStream.h"
|
|
||||||
|
|
||||||
typedef std::shared_ptr<PcmStream> PcmStreamPtr;
|
typedef std::shared_ptr<PcmStream> PcmStreamPtr;
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue