Merge pull request #120 from bondagit/daemon_v1.6.2

Daemon v1.6.2
This commit is contained in:
Andrea Bondavalli 2023-03-11 16:56:38 +01:00 committed by GitHub
commit ca207e8479
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 284 additions and 230 deletions

View File

@ -62,11 +62,13 @@ The daemon can be cross-compiled for multiple platforms and implements the follo
* IGMP handling for SAP, PTP and RTP sessions
* Integration with systemd watchdog monitoring (from daemon release v1.6)
The directory also contains the daemon regression tests in the [tests](daemon/tests) subdirectory.
See the [README](daemon/README.md) file in this directory for additional information about the AES67 daemon configuration and the HTTP REST API.
See the [README](daemon/README.md) file in this directory for additional information about the AES67 daemon configuration and the daemon HTTP REST API.
The directory also contains the daemon tests in the [tests](daemon/tests) subdirectory.
Daemon tests can be executed via a Docker container by using a fake version of the daemon driver manager.
This was implemented to perfom automated execution of the tests via a GitHub workflow.
Daemon regression tests can be executed via a Docker container by using a fake version of the daemon driver manager.
This was implmented to perfom automated execution of the regression tests via a GitHub workflow.
To build the Docker image for the daemon regression tests run:
docker build --progress=plain -f ./Dockerfile.daemon_tests -t aes67-daemon-tests .
@ -124,16 +126,16 @@ See [ALSA RAVENNA/AES67 Driver README](https://github.com/bondagit/aes67-linux-d
This directory contains systemd configuration files for the daemon.
The daemon integrates with systemd watchdog. To enable it recompile with the CMake option _-DWITH_SYSTEMD=ON_
The daemon integrates with systemd watchdog.
You can install the daemon under systemd using the following commands:
To enable it recompile the daemon with the CMake option _-DWITH_SYSTEMD=ON_
sudo useradd -M -l aes67-daemon -c "AES67 Linux daemon"
sudo cp daemon/aes67-daemon /usr/local/bin/aes67-daemon
sudo cp daemon/daemon.conf /etc
sudo cp systemd/aes67-daemon.service /etc/systemd/system
sudo systemctl enable aes67-daemon
sudo systemctl daemon-reexec
You can install the daemon under _systemd_ by using the script [systemd/install.sh](install.sh):
cd systemd
sudo ./install.sh
Before starting the daemon edit _/etc/daemon.conf_ and make sure the _interface_name_ parameter is set to your ethernet interface.
To start the daemon use:
@ -154,7 +156,6 @@ You can usally install the module using the following commands:
If this doesn't work because you miss kernel certificate follow the instructions at:
[No OpenSSL sign-file signing_key.pem](https://superuser.com/questions/1214116/no-openssl-sign-file-signing-key-pem-leads-to-error-while-loading-kernel-modules)
Finally use the command to load the modules:
sudo depmod -a
@ -320,8 +321,8 @@ and the specified buffer size cannot be used.
**_Important_** Starting from Linux kernel 5.10.x onwards a change in a kernel parameter is required to fix a problem with round robin scheduler causing the latency test to fail, see [Real Time Scheduler Throttling](#notes).
## Run the daemon regression tests ##
To run daemon regression tests install the ALSA RAVENNA/AES67 kernel module with:
## Run the daemon tests ##
To run daemon tests install the ALSA RAVENNA/AES67 kernel module with:
sudo insmod 3rdparty/ravenna-alsa-lkm/driver/MergingRavennaALSA.ko
@ -333,8 +334,6 @@ make sure that no instances of the aes67-daemon are running, enter the [tests](d
./daemon-test -p
**_NOTE:_** when running regression tests make sure that no other Ravenna mDNS sources are advertised on the network because this will affect the results. Regression tests run on loopback interface but Avahi ignores the interface parameter set and will forward to the daemon the sources found on all network interfaces.
## Notes ##
<a name="notes"></a>

View File

@ -1,9 +1,9 @@
# Docker
# Build a Docker image
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
# Azure pipeline to build and run daemon tests into Docker image
trigger:
- master
branches:
include:
- '*'
resources:
- repo: self
@ -11,17 +11,11 @@ resources:
variables:
tag: '$(Build.BuildId)'
stages:
- stage: Build
displayName: Build image
jobs:
- job: Build
displayName: Build
pool:
vmImage: ubuntu-latest
steps:
- task: Docker@2
displayName: Build an image
pool: Default
steps:
- task: Docker@2
displayName: Build and run daemon tests
inputs:
command: build
dockerfile: '$(Build.SourcesDirectory)/Dockerfile.daemon_tests'

View File

@ -96,6 +96,8 @@ bool Browser::worker() {
BOOST_LOG_TRIVIAL(debug)
<< "browser:: refreshing SAP source " << it->id;
// annoucement, update last seen and announce period
last_update_ =
duration_cast<second_t>(steady_clock::now() - startup_).count();
auto upd_source{*it};
if ((last_update_ - upd_source.last_seen) != 0) {
upd_source.announce_period = last_update_ - upd_source.last_seen;

View File

@ -57,7 +57,6 @@ class Browser : public MDNSClient {
Browser() = delete;
Browser(const Browser&) = delete;
Browser& operator=(const Browser&) = delete;
virtual ~Browser() { terminate(); };
bool init() override;
bool terminate() override;
@ -68,17 +67,17 @@ class Browser : public MDNSClient {
protected:
// singleton, use create() to build
Browser(std::shared_ptr<Config> config)
: MDNSClient(config), startup_(std::chrono::steady_clock::now()){};
explicit Browser(std::shared_ptr<Config> config) : MDNSClient(config){};
bool worker();
virtual void on_change_rtsp_source(const std::string& name,
void on_change_rtsp_source(const std::string& name,
const std::string& domain,
const RtspSource& source) override;
virtual void on_remove_rtsp_source(const std::string& name,
void on_remove_rtsp_source(const std::string& name,
const std::string& domain) override;
private:
std::future<bool> res_;
std::atomic_bool running_{false};
@ -99,7 +98,7 @@ class Browser : public MDNSClient {
SAP sap_{config_->get_sap_mcast_addr()};
IGMP igmp_;
std::chrono::time_point<std::chrono::steady_clock> startup_;
std::chrono::time_point<std::chrono::steady_clock> startup_{std::chrono::steady_clock::now()};
uint32_t last_update_{0}; /* seconds from daemon startup */
};

View File

@ -75,7 +75,6 @@ std::shared_ptr<Config> Config::parse(const std::string& filename,
if (ec) {
config.sap_mcast_addr_ = "224.2.127.254";
}
if (config.ptp_domain_ > 127)
if (config.ptp_domain_ > 127)
config.ptp_domain_ = 0;

View File

@ -35,7 +35,7 @@ class Config {
/* attributes retrieved from config json */
uint16_t get_http_port() const { return http_port_; };
uint16_t get_rtsp_port() const { return rtsp_port_; };
const std::string get_http_base_dir() const { return http_base_dir_; };
const std::string& get_http_base_dir() const { return http_base_dir_; };
int get_log_severity() const { return log_severity_; };
uint32_t get_playout_delay() const { return playout_delay_; };
uint32_t get_tic_frame_size_at_1fs() const { return tic_frame_size_at_1fs_; };
@ -64,14 +64,14 @@ class Config {
bool get_daemon_restart() const { return daemon_restart_; };
bool get_driver_restart() const { return driver_restart_; };
bool get_mdns_enabled() const { return mdns_enabled_; };
int get_interface_idx() { return interface_idx_; };
int get_interface_idx() const { return interface_idx_; };
const std::string& get_ptp_status_script() const {
return ptp_status_script_;
}
void set_http_port(uint16_t http_port) { http_port_ = http_port; };
void set_rtsp_port(uint16_t rtsp_port) { rtsp_port_ = rtsp_port; };
void set_http_base_dir(const std::string& http_base_dir) {
void set_http_base_dir(std::string_view http_base_dir) {
http_base_dir_ = http_base_dir;
};
void set_log_severity(int log_severity) { log_severity_ = log_severity; };
@ -85,10 +85,10 @@ class Config {
max_tic_frame_size_ = max_tic_frame_size;
};
void set_sample_rate(uint32_t sample_rate) { sample_rate_ = sample_rate; };
void set_rtp_mcast_base(const std::string& rtp_mcast_base) {
void set_rtp_mcast_base(std::string_view rtp_mcast_base) {
rtp_mcast_base_ = rtp_mcast_base;
};
void set_sap_mcast_addr(const std::string& sap_mcast_addr) {
void set_sap_mcast_addr(std::string_view sap_mcast_addr) {
sap_mcast_addr_ = sap_mcast_addr;
};
void set_rtp_port(uint16_t rtp_port) { rtp_port_ = rtp_port; };
@ -97,30 +97,30 @@ class Config {
void set_sap_interval(uint16_t sap_interval) {
sap_interval_ = sap_interval;
};
void set_syslog_proto(const std::string& syslog_proto) {
void set_syslog_proto(std::string_view syslog_proto) {
syslog_proto_ = syslog_proto;
};
void set_syslog_server(const std::string& syslog_server) {
void set_syslog_server(std::string_view syslog_server) {
syslog_server_ = syslog_server;
};
void set_status_file(const std::string& status_file) {
void set_status_file(std::string_view status_file) {
status_file_ = status_file;
};
void set_interface_name(const std::string& interface_name) {
void set_interface_name(std::string_view interface_name) {
interface_name_ = interface_name;
};
void set_ip_addr_str(const std::string& ip_str) { ip_str_ = ip_str; };
void set_ip_addr_str(std::string_view ip_str) { ip_str_ = ip_str; };
void set_ip_addr(uint32_t ip_addr) { ip_addr_ = ip_addr; };
void set_mac_addr_str(const std::string& mac_str) { mac_str_ = mac_str; };
void set_mac_addr_str(std::string_view mac_str) { mac_str_ = mac_str; };
void set_mac_addr(const std::array<uint8_t, 6>& mac_addr) {
mac_addr_ = mac_addr;
};
void set_mdns_enabled(bool enabled) { mdns_enabled_ = enabled; };
void set_interface_idx(int index) { interface_idx_ = index; };
void set_ptp_status_script(const std::string& script) {
void set_ptp_status_script(std::string_view script) {
ptp_status_script_ = script;
};
void set_custom_node_id(const std::string& node_id) {
void set_custom_node_id(std::string_view node_id) {
custom_node_id_ = node_id;
};
void set_auto_sinks_update(bool auto_sinks_update) {

View File

@ -54,7 +54,7 @@ void DriverHandler::send(enum MT_ALSA_msg_id id,
NetlinkClient& client,
uint8_t* buffer,
size_t data_size,
const uint8_t* data) {
const uint8_t* data) const {
struct MT_ALSA_msg alsa_msg;
memset(&alsa_msg, 0, sizeof(alsa_msg));
alsa_msg.id = id;
@ -143,7 +143,7 @@ bool DriverHandler::terminate(const Config& /* config */) {
void DriverHandler::send_command(enum MT_ALSA_msg_id id,
size_t data_size,
const uint8_t* data) {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock<std::mutex> lock{mutex_};
if (data_size > max_payload) {
on_command_error(id, DaemonErrc::send_invalid_size);
return;

View File

@ -37,7 +37,7 @@ class DriverHandler {
static constexpr size_t buffer_size =
NLMSG_SPACE(max_payload) + sizeof(struct MT_ALSA_msg);
DriverHandler(){};
DriverHandler() = default;
DriverHandler(const DriverHandler&) = delete;
DriverHandler& operator=(const DriverHandler&) = delete;
virtual ~DriverHandler(){};
@ -67,7 +67,7 @@ class DriverHandler {
NetlinkClient& client,
uint8_t* buffer,
size_t data_size,
const uint8_t* data);
const uint8_t* data) const;
bool event_receiver();
std::future<bool> res_;

View File

@ -75,7 +75,7 @@ bool DriverManager::init(const Config& config) {
return false;
}
sample_rate = config.get_sample_rate();
sample_rate_ = config.get_sample_rate();
TPTPConfig ptp_config;
ptp_config.ui8Domain = config.get_ptp_domain();
@ -287,39 +287,41 @@ void DriverManager::on_event(enum MT_ALSA_msg_id id,
break;
case MT_ALSA_Msg_SetMasterOutputVolume:
if (req_size == sizeof(int32_t)) {
memcpy(&output_volume, req, req_size);
memcpy(&output_volume_, req, req_size);
BOOST_LOG_TRIVIAL(info)
<< "driver_manager:: event SetMasterOutputVolume " << output_volume;
<< "driver_manager:: event SetMasterOutputVolume "
<< output_volume_;
}
resp_size = 0;
break;
case MT_ALSA_Msg_SetMasterOutputSwitch:
if (req_size == sizeof(int32_t)) {
memcpy(&output_switch, req, req_size);
memcpy(&output_switch_, req, req_size);
BOOST_LOG_TRIVIAL(info)
<< "driver_manager:: event SetMasterOutputSwitch " << output_switch;
<< "driver_manager:: event SetMasterOutputSwitch "
<< output_switch_;
}
resp_size = 0;
break;
case MT_ALSA_Msg_SetSampleRate:
if (req_size == sizeof(uint32_t)) {
memcpy(&sample_rate, req, req_size);
memcpy(&sample_rate_, req, req_size);
BOOST_LOG_TRIVIAL(info)
<< "driver_manager:: event SetSampleRate " << sample_rate;
<< "driver_manager:: event SetSampleRate " << sample_rate_;
}
resp_size = 0;
break;
case MT_ALSA_Msg_GetMasterOutputVolume:
resp_size = sizeof(int32_t);
memcpy(resp, &output_volume, resp_size);
memcpy(resp, &output_volume_, resp_size);
BOOST_LOG_TRIVIAL(info)
<< "driver_manager:: event GetMasterOutputVolume " << output_volume;
<< "driver_manager:: event GetMasterOutputVolume " << output_volume_;
break;
case MT_ALSA_Msg_GetMasterOutputSwitch:
resp_size = sizeof(int32_t);
memcpy(resp, &output_switch, resp_size);
memcpy(resp, &output_switch_, resp_size);
BOOST_LOG_TRIVIAL(info)
<< "driver_manager:: event GetMasterOutputSwitch " << output_switch;
<< "driver_manager:: event GetMasterOutputSwitch " << output_switch_;
break;
default:
BOOST_LOG_TRIVIAL(error) << "driver_manager:: unknown event "

View File

@ -53,13 +53,13 @@ class DriverManager : public DriverHandler {
std::error_code get_number_of_inputs(int32_t& inputs);
std::error_code get_number_of_outputs(int32_t& outputs);
int32_t get_current_output_volume() { return output_volume; };
int32_t get_current_output_switch() { return output_switch; };
uint32_t get_current_sample_rate() { return sample_rate; };
int32_t get_current_output_volume() const { return output_volume_; };
int32_t get_current_output_switch() const { return output_switch_; };
uint32_t get_current_sample_rate() const { return sample_rate_; };
protected:
// singleton, use create to build
DriverManager(){};
DriverManager() = default;
// these are used in init/terminate
std::error_code hello();
@ -79,12 +79,13 @@ class DriverManager : public DriverHandler {
const uint8_t* req = nullptr) override;
void on_event_error(enum MT_ALSA_msg_id id, std::error_code error) override;
private:
std::error_code retcode_;
uint8_t recv_data_[NLMSG_SPACE(max_payload)]{0};
int32_t output_volume{-20};
int32_t output_switch{0};
uint32_t sample_rate{0};
int32_t output_volume_{-20};
int32_t output_switch_{0};
uint32_t sample_rate_{0};
};
#endif

View File

@ -120,7 +120,8 @@ std::error_code DriverManager::add_rtp_stream(
uint64_t& stream_handle) {
stream_handle = ++g_handle;
handles_.insert(stream_handle);
BOOST_LOG_TRIVIAL(info) << "fake_driver_manager:: add RTP stream success handle "
BOOST_LOG_TRIVIAL(info)
<< "fake_driver_manager:: add RTP stream success handle "
<< stream_handle;
return std::error_code{};
}
@ -170,7 +171,8 @@ std::error_code DriverManager::set_playout_delay(int32_t delay) {
std::error_code DriverManager::get_sample_rate(uint32_t& sample_rate) {
sample_rate = sample_rate_;
BOOST_LOG_TRIVIAL(info) << "fake_driver_manager:: sample rate " << sample_rate;
BOOST_LOG_TRIVIAL(info) << "fake_driver_manager:: sample rate "
<< sample_rate;
return std::error_code{};
}

View File

@ -52,13 +52,13 @@ class DriverManager {
std::error_code get_number_of_inputs(int32_t& inputs);
std::error_code get_number_of_outputs(int32_t& outputs);
int32_t get_current_output_volume() { return output_volume_; };
int32_t get_current_output_switch() { return output_switch_; };
uint32_t get_current_sample_rate() { return sample_rate_; };
int32_t get_current_output_volume() const { return output_volume_; };
int32_t get_current_output_switch() const { return output_switch_; };
uint32_t get_current_sample_rate() const { return sample_rate_; };
protected:
// singleton, use create to build
DriverManager(){};
DriverManager() = default;
// these are used in init/terminate
std::error_code hello();

View File

@ -31,7 +31,7 @@
using namespace httplib;
static inline void set_headers(Response& res,
const std::string content_type = "") {
const std::string& content_type = "") {
res.set_header("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS");
res.set_header("Access-Control-Allow-Origin", "*");
@ -57,7 +57,6 @@ static inline int get_http_error_status(const std::error_code& code) {
static inline std::string get_http_error_message(const std::error_code& code) {
std::stringstream ss;
;
ss << "(" << code.category().name() << ") " << code.message();
return ss.str();
}
@ -352,11 +351,12 @@ bool HttpServer::init() {
httplib::Client cli(config_->get_ip_addr_str().c_str(),
config_->get_http_port());
int retry = 3;
while (retry--) {
while (retry) {
auto res = cli.Get("/api/config");
if (res && res->status == 200) {
break;
}
--retry;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return retry;

View File

@ -29,7 +29,7 @@
class HttpServer {
public:
HttpServer() = delete;
HttpServer(std::shared_ptr<SessionManager> session_manager,
explicit HttpServer(std::shared_ptr<SessionManager> session_manager,
std::shared_ptr<Browser> browser,
std::shared_ptr<Config> config)
: session_manager_(session_manager), browser_(browser), config_(config){};

View File

@ -41,11 +41,11 @@ class IGMP {
bool join(const std::string& interface_ip, const std::string& mcast_ip) {
uint32_t mcast_ip_addr =
ip::address_v4::from_string(mcast_ip.c_str()).to_ulong();
std::lock_guard<std::mutex> lock(mutex);
std::scoped_lock<std::mutex> lock{mutex};
auto it = mcast_ref.find(mcast_ip_addr);
if (it != mcast_ref.end() && (*it).second > 0) {
mcast_ref[mcast_ip_addr]++;
(*it).second++;
return true;
}
@ -76,14 +76,14 @@ class IGMP {
bool leave(const std::string& interface_ip, const std::string& mcast_ip) {
uint32_t mcast_ip_addr =
ip::address_v4::from_string(mcast_ip.c_str()).to_ulong();
std::lock_guard<std::mutex> lock(mutex);
std::scoped_lock<std::mutex> lock{mutex};
auto it = mcast_ref.find(mcast_ip_addr);
if (it == mcast_ref.end() || (*it).second == 0) {
return false;
}
if (--mcast_ref[mcast_ip_addr] > 0) {
if (--(*it).second > 0) {
return true;
}

View File

@ -127,7 +127,7 @@ std::pair<std::array<uint8_t, 6>, std::string> get_mac_from_arp_cache(
std::vector<std::string> tokens;
std::getline(stream, line);
if (line.find(ip)) {
if (line.find(ip) == std::string::npos) {
continue;
}
boost::split(tokens, line, boost::is_any_of(" "), boost::token_compress_on);
@ -139,8 +139,8 @@ std::pair<std::array<uint8_t, 6>, std::string> get_mac_from_arp_cache(
int j = 0;
bool check = false;
for (auto const& item : vec) {
mac[j] = strtol(item.c_str(), NULL, 16);
check |= mac[j];
mac[j] = strtol(item.c_str(), nullptr, 16);
check = check | (mac[j] != 0);
j++;
}
if (check) {

View File

@ -29,7 +29,7 @@
#include "json.hpp"
static inline std::string remove_undesired_chars(const std::string& s) {
std::regex html_regex("[^ A-Za-z0-9:~.,_/=%\()\\r\\n\\t\?#-]?");
std::regex html_regex("[^ A-Za-z0-9:~.,_/=%()\\r\\n\\t\?#-]?");
return std::regex_replace(s, html_regex, "");
}

View File

@ -39,7 +39,7 @@ namespace po = boost::program_options;
namespace postyle = boost::program_options::command_line_style;
namespace logging = boost::log;
static std::string version("bondagit-1.6.1");
static const std::string version("bondagit-1.6.2");
static std::atomic<bool> terminate = false;
void termination_handler(int signum) {

View File

@ -87,7 +87,7 @@ void MDNSClient::resolve_callback(AvahiServiceResolver* r,
(mdns.config_->get_interface_name() != "lo")) ||
((flags & AVAHI_LOOKUP_RESULT_LOCAL) &&
(mdns.config_->get_interface_name() == "lo"))) {
std::lock_guard<std::mutex> lock(mdns.sources_res_mutex_);
std::scoped_lock<std::mutex> lock{mdns.sources_res_mutex_};
/* process RTSP client in async task */
mdns.sources_res_.emplace_back(std::async(
@ -153,7 +153,7 @@ void MDNSClient::browse_callback(AvahiServiceBrowser* b,
<< avahi_strerror(avahi_client_errno(mdns.client_.get()));
} else {
/* add the resolver to the active pool */
mdns.active_resolvers.insert({name, domain});
mdns.active_resolvers.emplace(name, domain);
}
break;
@ -244,7 +244,7 @@ bool MDNSClient::init() {
void MDNSClient::process_results() {
#ifdef _USE_AVAHI_
std::lock_guard<std::mutex> lock(sources_res_mutex_);
std::scoped_lock<std::mutex> lock{sources_res_mutex_};
/* remove all completed results and populate remote sources list */
sources_res_.remove_if([](auto& result) {
if (!result.valid()) {
@ -269,7 +269,7 @@ bool MDNSClient::terminate() {
RtspClient::stop_all();
#ifdef _USE_AVAHI_
/* wait for all pending results and remove from list */
std::lock_guard<std::mutex> lock(sources_res_mutex_);
std::scoped_lock<std::mutex> lock{sources_res_mutex_};
BOOST_LOG_TRIVIAL(info) << "mdns_client:: waiting for "
<< sources_res_.size() << " RTSP clients";
sources_res_.remove_if([](auto& result) {

View File

@ -39,11 +39,11 @@
class MDNSClient {
public:
MDNSClient(std::shared_ptr<Config> config) : config_(config){};
explicit MDNSClient(std::shared_ptr<Config> config) : config_(config){};
MDNSClient() = delete;
MDNSClient(const MDNSClient&) = delete;
MDNSClient& operator=(const MDNSClient&) = delete;
virtual ~MDNSClient() { terminate(); };
virtual ~MDNSClient() = default;
virtual bool init();
virtual bool terminate();
@ -55,22 +55,7 @@ class MDNSClient {
virtual void on_remove_rtsp_source(const std::string& name,
const std::string& domain){};
void process_results();
std::list<std::future<void> > sources_res_;
std::mutex sources_res_mutex_;
std::atomic_bool running_{false};
std::shared_ptr<Config> config_;
#ifdef _USE_AVAHI_
/* order is important here */
std::unique_ptr<AvahiThreadedPoll, decltype(&avahi_threaded_poll_free)> poll_{
nullptr, &avahi_threaded_poll_free};
std::unique_ptr<AvahiClient, decltype(&avahi_client_free)> client_{
nullptr, &avahi_client_free};
std::unique_ptr<AvahiServiceBrowser, decltype(&avahi_service_browser_free)>
sb_{nullptr, &avahi_service_browser_free};
static void resolve_callback(AvahiServiceResolver* r,
AvahiIfIndex interface,
AvahiProtocol protocol,
@ -96,10 +81,28 @@ class MDNSClient {
static void client_callback(AvahiClient* c,
AvahiClientState state,
void* userdata);
#endif
void process_results();
std::shared_ptr<Config> config_;
private:
std::list<std::future<void> > sources_res_;
std::mutex sources_res_mutex_;
std::atomic_bool running_{false};
#ifdef _USE_AVAHI_
/* order is important here */
std::unique_ptr<AvahiThreadedPoll, decltype(&avahi_threaded_poll_free)> poll_{
nullptr, &avahi_threaded_poll_free};
std::unique_ptr<AvahiClient, decltype(&avahi_client_free)> client_{
nullptr, &avahi_client_free};
std::unique_ptr<AvahiServiceBrowser, decltype(&avahi_service_browser_free)>
sb_{nullptr, &avahi_service_browser_free};
std::set<std::pair<std::string /*name*/, std::string /*domain */> >
active_resolvers;
#endif
};

View File

@ -28,7 +28,7 @@
#ifdef _USE_AVAHI_
struct AvahiLockGuard {
AvahiLockGuard() = delete;
AvahiLockGuard(AvahiThreadedPoll* poll) : poll_(poll) {
explicit AvahiLockGuard(AvahiThreadedPoll* poll) : poll_(poll) {
if (poll_ != nullptr) {
avahi_threaded_poll_lock(poll_);
}
@ -38,6 +38,8 @@ struct AvahiLockGuard {
avahi_threaded_poll_unlock(poll_);
}
}
AvahiLockGuard(const AvahiLockGuard&) = delete;
AvahiLockGuard& operator=(const AvahiLockGuard&) = delete;
private:
AvahiThreadedPoll* poll_{nullptr};

View File

@ -46,15 +46,27 @@ class MDNSServer {
MDNSServer() = delete;
MDNSServer(const MDNSServer&) = delete;
MDNSServer& operator=(const MDNSServer&) = delete;
virtual ~MDNSServer() { terminate(); };
virtual ~MDNSServer() = default;
virtual bool init();
virtual bool terminate();
bool add_service(const std::string& name, const std::string& sdp);
bool add_service(const std::string& name, const std::string &sdp);
bool remove_service(const std::string& name);
protected:
#ifdef _USE_AVAHI_
static void entry_group_callback(AvahiEntryGroup* g,
AvahiEntryGroupState state,
void* userdata);
static void client_callback(AvahiClient* c,
AvahiClientState state,
void* userdata);
bool create_services(AvahiClient* client);
#endif
private:
std::atomic_bool running_{false};
std::shared_ptr<SessionManager> session_manager_;
std::shared_ptr<Config> config_;
@ -72,14 +84,6 @@ class MDNSServer {
std::unique_ptr<AvahiClient, decltype(&avahi_client_free)> client_{
nullptr, &avahi_client_free};
static void entry_group_callback(AvahiEntryGroup* g,
AvahiEntryGroupState state,
void* userdata);
static void client_callback(AvahiClient* c,
AvahiClientState state,
void* userdata);
bool create_services(AvahiClient* client);
#endif
};

View File

@ -26,62 +26,55 @@
template <typename Protocol>
class nl_endpoint {
private:
sockaddr_nl sockaddr{.nl_family = AF_NETLINK};
sockaddr_nl sockaddr_{.nl_family = AF_NETLINK};
public:
using protocol_type = Protocol;
using data_type = boost::asio::detail::socket_addr_type;
nl_endpoint() {
sockaddr.nl_groups = 0;
sockaddr.nl_pid = getpid();
sockaddr_.nl_groups = 0;
sockaddr_.nl_pid = getpid();
}
nl_endpoint(int group, int pid = getpid()) {
sockaddr.nl_groups = group;
sockaddr.nl_pid = pid;
}
nl_endpoint(const nl_endpoint& other) { sockaddr = other.sockaddr; }
nl_endpoint& operator=(const nl_endpoint& other) {
sockaddr = other.sockaddr;
return *this;
sockaddr_.nl_groups = group;
sockaddr_.nl_pid = pid;
}
protocol_type protocol() const { return protocol_type(); }
data_type* data() { return reinterpret_cast<struct sockaddr*>(&sockaddr); }
data_type* data() { return reinterpret_cast<struct sockaddr*>(&sockaddr_); }
const data_type* data() const {
return reinterpret_cast<const struct sockaddr*>(&sockaddr);
return reinterpret_cast<const struct sockaddr*>(&sockaddr_);
}
std::size_t size() const { return sizeof(sockaddr); }
std::size_t size() const { return sizeof(sockaddr_); }
void resize(std::size_t size) { /* nothing we can do here */
}
std::size_t capacity() const { return sizeof(sockaddr); }
std::size_t capacity() const { return sizeof(sockaddr_); }
};
class nl_protocol {
public:
nl_protocol() { proto = 0; }
nl_protocol() : proto_(0) {}
explicit nl_protocol(int proto) { this->proto = proto; }
explicit nl_protocol(int proto) : proto_(proto) {}
int type() const { return SOCK_RAW; }
int protocol() const { return proto; }
int protocol() const { return proto_; }
int family() const { return PF_NETLINK; }
typedef nl_endpoint<nl_protocol> endpoint;
typedef boost::asio::basic_raw_socket<nl_protocol> socket;
using endpoint = nl_endpoint<nl_protocol>;
using socket = boost::asio::basic_raw_socket<nl_protocol>;
private:
int proto;
int proto_;
};
#endif

View File

@ -35,7 +35,7 @@ using boost::asio::deadline_timer;
class NetlinkClient {
public:
NetlinkClient() = delete;
NetlinkClient(const std::string& name) : name_(name) {}
explicit NetlinkClient(const std::string& name) : name_(name) {}
void init(const nl_endpoint<nl_protocol>& listen_endpoint,
const nl_protocol& protocol) {

View File

@ -98,14 +98,13 @@ struct RtspActiveClientRemover {
domain_(domain),
wait_for_updates_(wait_for_updates) {
if (stream_ != nullptr && wait_for_updates_) {
RtspClient::g_mutex.lock();
std::scoped_lock<std::mutex> lock{RtspClient::g_mutex};
RtspClient::g_active_clients[{name_, domain_}] = stream_;
RtspClient::g_mutex.unlock();
}
}
~RtspActiveClientRemover() {
if (stream_ != nullptr && wait_for_updates_) {
std::lock_guard<std::mutex> lock(RtspClient::g_mutex);
std::scoped_lock<std::mutex> lock{RtspClient::g_mutex};
auto it = RtspClient::g_active_clients.find({name_, domain_});
if (it != RtspClient::g_active_clients.end() && it->second == stream_) {
RtspClient::g_active_clients.erase(it);
@ -113,6 +112,9 @@ struct RtspActiveClientRemover {
}
}
RtspActiveClientRemover(const RtspActiveClientRemover&) = delete;
RtspActiveClientRemover& operator=(const RtspActiveClientRemover&) = delete;
private:
ip::tcp::iostream* stream_{nullptr};
const std::string& name_;
@ -120,7 +122,8 @@ struct RtspActiveClientRemover {
bool wait_for_updates_{false};
};
std::pair<bool, RtspSource> RtspClient::process(RtspClient::Observer callback,
std::pair<bool, RtspSource> RtspClient::process(
const RtspClient::Observer& callback,
const std::string& name,
const std::string& domain,
const std::string& path,
@ -258,9 +261,9 @@ std::pair<bool, RtspSource> RtspClient::process(RtspClient::Observer callback,
if (std::get<0>(res)) {
/* if we find a valid announced source name we use it
* otherwise we try from SDP file or we use the mDNS name */
auto path = std::get<4>(res);
const auto& lpath = std::get<4>(res);
if (path.rfind("/by-name/") != std::string::npos) {
announced_name = path.substr(9);
announced_name = lpath.substr(9);
BOOST_LOG_TRIVIAL(debug)
<< "rtsp_client:: found announced name " << announced_name;
}
@ -279,7 +282,7 @@ std::pair<bool, RtspSource> RtspClient::process(RtspClient::Observer callback,
}
void RtspClient::stop(const std::string& name, const std::string& domain) {
std::lock_guard<std::mutex> lock(g_mutex);
std::scoped_lock<std::mutex> lock{g_mutex};
auto it = g_active_clients.find({name, domain});
if (it != g_active_clients.end()) {
BOOST_LOG_TRIVIAL(info)
@ -294,12 +297,12 @@ void RtspClient::stop(const std::string& name, const std::string& domain) {
}
bool RtspClient::is_active(const std::string& name, const std::string& domain) {
std::lock_guard<std::mutex> lock(g_mutex);
std::scoped_lock<std::mutex> lock{g_mutex};
return g_active_clients.find({name, domain}) != g_active_clients.end();
}
void RtspClient::stop_all() {
std::lock_guard<std::mutex> lock(g_mutex);
std::scoped_lock<std::mutex> lock{g_mutex};
auto it = g_active_clients.begin();
while (it != g_active_clients.end()) {
BOOST_LOG_TRIVIAL(info) << "rtsp_client:: stopping client "

View File

@ -41,7 +41,7 @@ class RtspClient {
const std::string& domain,
const RtspSource& source)>;
static std::pair<bool, RtspSource> process(Observer callback,
static std::pair<bool, RtspSource> process(const Observer& callback,
const std::string& name,
const std::string& domain,
const std::string& path,
@ -57,11 +57,11 @@ class RtspClient {
const std::string& address,
const std::string& port = dft_port);
inline static std::atomic<uint16_t> g_seq_number{0};
inline static std::map<
std::pair<std::string /*name*/, std::string /*domain*/>,
boost::asio::ip::tcp::iostream* /*stream*/>
g_active_clients;
inline static std::atomic<uint16_t> g_seq_number{0};
inline static std::mutex g_mutex;
};

View File

@ -25,7 +25,7 @@ bool RtspServer::update_source(uint8_t id,
const std::string& sdp) {
bool ret = false;
BOOST_LOG_TRIVIAL(debug) << "rtsp_server:: added source " << name;
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock<std::mutex> lock{mutex_};
for (unsigned int i = 0; i < sessions_.size(); i++) {
auto session = sessions_[i].lock();
if (session != nullptr) {
@ -39,7 +39,7 @@ bool RtspServer::update_source(uint8_t id,
void RtspServer::accept() {
acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
if (!ec) {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock<std::mutex> lock{mutex_};
/* check for free sessions */
unsigned int i = 0;
for (; i < sessions_.size(); i++) {
@ -73,8 +73,8 @@ bool RtspSession::announce(uint8_t id,
* and the specified source id has been described on this session send update
*/
if (cseq_ < 0 && source_ids_.find(id) != source_ids_.end()) {
std::string path(std::string("/by-name/") +
config_->get_node_id() + " " + name);
std::string path(std::string("/by-name/") + config_->get_node_id() + " " +
name);
std::stringstream ss;
ss << "ANNOUNCE rtsp://" << address << ":" << std::to_string(port)
<< httplib::detail::encode_url(path) << " RTSP/1.0\r\n"
@ -170,17 +170,18 @@ void RtspSession::build_response(const std::string& url) {
send_error(400, "Bad Request");
return;
}
auto path = std::get<4>(res);
auto base_path =
std::string("/by-name/") + config_->get_node_id() + " ";
const auto& path = std::get<4>(res);
auto base_path = std::string("/by-name/") + config_->get_node_id() + " ";
uint8_t id = SessionManager::stream_id_max + 1;
if (path.rfind(base_path) != std::string::npos) {
/* extract the source name from path and retrive the id */
id = session_manager_->get_source_id(path.substr(base_path.length()));
} else if (path.rfind("/by-id/") != std::string::npos) {
try {
id = stoi(path.substr(7));
id = (uint8_t)stoi(path.substr(7));
} catch (...) {
id = SessionManager::stream_id_max + 1;
;
}
}
if (id != (SessionManager::stream_id_max + 1)) {

View File

@ -44,10 +44,7 @@ class RtspSession : public std::enable_shared_from_this<RtspSession> {
tcp::socket socket)
: config_(config),
session_manager_(session_manager),
socket_(std::move(socket)),
length_{0},
cseq_{-1},
consumed_{0} {}
socket_(std::move(socket)) {}
virtual ~RtspSession() {
BOOST_LOG_TRIVIAL(debug) << "rtsp_server:: session end";
@ -87,18 +84,14 @@ class RtspServer {
2};
RtspServer() = delete;
RtspServer(std::shared_ptr<SessionManager> session_manager,
explicit RtspServer(std::shared_ptr<SessionManager> session_manager,
std::shared_ptr<Config> config)
: session_manager_(session_manager),
config_(config),
sessions_(session_num_max),
sessions_start_point_(session_num_max),
acceptor_(io_service_,
tcp::endpoint(boost::asio::ip::address::from_string(
config_->get_ip_addr_str()),
config_->get_rtsp_port())),
socket_(io_service_) {}
config_->get_rtsp_port())) {}
bool init() {
accept();
/* start rtsp server on a separate thread */
@ -135,8 +128,8 @@ class RtspServer {
boost::asio::io_service io_service_;
std::shared_ptr<SessionManager> session_manager_;
std::shared_ptr<Config> config_;
std::vector<std::weak_ptr<RtspSession> > sessions_;
std::vector<time_point<steady_clock> > sessions_start_point_;
std::vector<std::weak_ptr<RtspSession> > sessions_{session_num_max};
std::vector<time_point<steady_clock> > sessions_start_point_{session_num_max};
tcp::acceptor acceptor_;
tcp::socket socket_{io_service_};
std::future<void> res_;

View File

@ -106,7 +106,7 @@ bool SAP::receive(bool& is_announce,
is_announce = (buffer[0] == 0x20);
memcpy(&msg_id_hash, buffer + 2, sizeof(msg_id_hash));
memcpy(&addr, buffer + 4, sizeof(addr));
for (int i = 8; buffer[i] != 0 && i < static_cast<int>(length); i++) {
for (int i = 8; i < static_cast<int>(length) && buffer[i] != 0; i++) {
buffer[i] = std::tolower(buffer[i]);
}
if (!memcmp(buffer + 8, "application/sdp", 16)) {

View File

@ -37,7 +37,7 @@ class SAP {
constexpr static uint16_t max_length = 4096;
SAP() = delete;
SAP(const std::string& sap_mcast_addr);
explicit SAP(const std::string& sap_mcast_addr);
bool set_multicast_interface(const std::string& interface_ip);
bool announcement(uint16_t msg_id_hash,

View File

@ -38,7 +38,7 @@
#include "session_manager.hpp"
#include "interface.hpp"
static uint8_t get_codec_word_length(const std::string& codec) {
static uint8_t get_codec_word_length(std::string_view codec) {
if (codec == "L16") {
return 2;
}
@ -60,7 +60,7 @@ static uint8_t get_codec_word_length(const std::string& codec) {
return 0;
}
bool SessionManager::parse_sdp(const std::string sdp, StreamInfo& info) const {
bool SessionManager::parse_sdp(const std::string& sdp, StreamInfo& info) const {
/*
v=0
o=- 4 0 IN IP4 10.0.0.12
@ -409,7 +409,7 @@ bool SessionManager::load_status() {
return true;
}
bool SessionManager::save_status() {
bool SessionManager::save_status() const {
if (config_->get_status_file().empty()) {
return true;
}
@ -443,7 +443,8 @@ uint8_t SessionManager::get_source_id(const std::string& name) const {
return it != source_names_.end() ? it->second : (stream_id_max + 1);
}
void SessionManager::add_source_observer(ObserverType type, Observer cb) {
void SessionManager::add_source_observer(ObserverType type,
const Observer& cb) {
switch (type) {
case ObserverType::add_source:
add_source_observers.push_back(cb);
@ -459,7 +460,7 @@ void SessionManager::add_source_observer(ObserverType type, Observer cb) {
void SessionManager::on_add_source(const StreamSource& source,
const StreamInfo& info) {
for (auto cb : add_source_observers) {
for (const auto& cb : add_source_observers) {
cb(source.id, source.name, get_source_sdp_(source.id, info));
}
if (IN_MULTICAST(info.stream.m_ui32DestIP)) {
@ -470,8 +471,8 @@ void SessionManager::on_add_source(const StreamSource& source,
}
void SessionManager::on_remove_source(const StreamInfo& info) {
for (auto cb : remove_source_observers) {
cb(info.stream.m_uiId, info.stream.m_cName, {});
for (const auto& cb : remove_source_observers) {
cb((uint8_t)info.stream.m_uiId, info.stream.m_cName, {});
}
if (IN_MULTICAST(info.stream.m_ui32DestIP)) {
igmp_.leave(config_->get_ip_addr_str(),
@ -531,13 +532,14 @@ std::error_code SessionManager::add_source(const StreamSource& source) {
config_->get_interface_name(),
ip::address_v4(info.stream.m_ui32DestIP).to_string());
int retry = 3;
while (!mac_addr.second.length() && retry--) {
while (!mac_addr.second.length() && retry > 0) {
// if not in cache already try to populate the MAC cache
(void)echo_try_connect(
ip::address_v4(info.stream.m_ui32DestIP).to_string());
mac_addr = get_mac_from_arp_cache(
config_->get_interface_name(),
ip::address_v4(info.stream.m_ui32DestIP).to_string());
retry--;
}
if (!mac_addr.second.length()) {
BOOST_LOG_TRIVIAL(error)
@ -693,8 +695,7 @@ std::error_code SessionManager::remove_source(uint32_t id) {
}
std::error_code ret;
const auto& info = (*it).second;
if (info.enabled) {
if (const auto& info = (*it).second; info.enabled) {
ret = driver_->remove_rtp_stream(info.handle);
if (!ret) {
on_remove_source(info);
@ -808,7 +809,7 @@ std::error_code SessionManager::add_sink(const StreamSink& sink) {
return DaemonErrc::cannot_parse_sdp;
}
info.sink_sdp = std::move(sink.sdp);
info.sink_sdp = sink.sdp;
}
info.sink_source = sink.source;
info.sink_use_sdp = true; // save back and use with SDP file
@ -922,7 +923,7 @@ std::error_code SessionManager::get_sink_status(
return ret;
}
std::error_code SessionManager::set_driver_config(const std::string& name,
std::error_code SessionManager::set_driver_config(std::string_view name,
uint32_t value) const {
if (name == "sample_rate")
return driver_->set_sample_rate(value);
@ -1076,7 +1077,7 @@ void SessionManager::on_update_sources() {
// trigger sources SDP file update
sources_mutex_.lock();
for (auto& [id, info] : sources_) {
for (auto cb : update_source_observers) {
for (const auto& cb : update_source_observers) {
info.session_version++;
cb(id, info.stream.m_cName, get_source_sdp_(id, info));
}
@ -1104,7 +1105,7 @@ void SessionManager::on_ptp_status_changed(const std::string& status) const {
char* argv_list[] = {
const_cast<char*>(config_->get_ptp_status_script().c_str()),
const_cast<char*>(status.c_str()), NULL};
const_cast<char*>(status.c_str()), nullptr};
execv(config_->get_ptp_status_script().c_str(), argv_list);
exit(0);
@ -1142,7 +1143,8 @@ bool SessionManager::worker() {
// return false;
} else {
char ptp_clock_id[24];
uint8_t* pui64GMID = reinterpret_cast<uint8_t*>(&ptp_status.ui64GMID);
const uint8_t* pui64GMID =
reinterpret_cast<uint8_t*>(&ptp_status.ui64GMID);
snprintf(ptp_clock_id, sizeof(ptp_clock_id),
"%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X", pui64GMID[0],
pui64GMID[1], pui64GMID[2], pui64GMID[3], pui64GMID[4],

View File

@ -86,7 +86,7 @@ struct PTPStatus {
struct StreamInfo {
TRTP_stream_info stream;
uint64_t handle{0};
bool enabled{0};
bool enabled{false};
bool refclk_ptp_traceable{false};
bool ignore_refclk_gmid{false};
std::string io;
@ -109,7 +109,7 @@ class SessionManager {
SessionManager() = delete;
SessionManager(const SessionManager&) = delete;
SessionManager& operator=(const SessionManager&) = delete;
virtual ~SessionManager() { terminate(); };
virtual ~SessionManager() = default;
// session manager interface
bool init() {
@ -127,10 +127,10 @@ class SessionManager {
if (running_) {
running_ = false;
auto ret = res_.get();
for (auto source : get_sources()) {
for (const auto& source : get_sources()) {
remove_source(source.id);
}
for (auto sink : get_sinks()) {
for (const auto& sink : get_sinks()) {
remove_sink(sink.id);
}
return ret;
@ -148,7 +148,7 @@ class SessionManager {
enum class ObserverType { add_source, remove_source, update_source };
using Observer = std::function<
bool(uint8_t id, const std::string& name, const std::string& sdp)>;
void add_source_observer(ObserverType type, Observer cb);
void add_source_observer(ObserverType type, const Observer& cb);
std::error_code add_sink(const StreamSink& sink);
std::error_code get_sink(uint8_t id, StreamSink& sink) const;
@ -158,13 +158,13 @@ class SessionManager {
uint8_t get_sink_id(const std::string& name) const;
std::error_code set_ptp_config(const PTPConfig& config);
std::error_code set_driver_config(const std::string& name,
std::error_code set_driver_config(std::string_view name,
uint32_t value) const;
void get_ptp_config(PTPConfig& config) const;
void get_ptp_status(PTPStatus& status) const;
bool load_status();
bool save_status();
bool save_status() const;
size_t process_sap();
@ -197,10 +197,10 @@ class SessionManager {
bool sink_is_still_valid(const std::string sdp,
const std::list<RemoteSource> sources_list) const;
bool parse_sdp(const std::string sdp, StreamInfo& info) const;
bool parse_sdp(const std::string& sdp, StreamInfo& info) const;
bool worker();
// singleton, use create() to build
SessionManager(std::shared_ptr<DriverManager> driver,
explicit SessionManager(std::shared_ptr<DriverManager> driver,
std::shared_ptr<Browser> browser,
std::shared_ptr<Config> config)
: browser_(browser), driver_(driver), config_(config) {
@ -208,6 +208,7 @@ class SessionManager {
ptp_config_.dscp = config->get_ptp_dscp();
};
private:
std::shared_ptr<Browser> browser_;
std::shared_ptr<DriverManager> driver_;
std::shared_ptr<Config> config_;

View File

@ -70,7 +70,8 @@ struct DaemonInstance {
auto pid = daemon_.native_handle();
/* trigger normal daemon termination */
kill(pid, SIGTERM);
daemon_.wait();
std::error_code ec;
daemon_.wait(ec);
BOOST_REQUIRE_MESSAGE(!daemon_.exit_code(), "daemon exited normally");
ok = false;
}

View File

@ -99,7 +99,7 @@ std::string sdp_get_subject(const std::string& sdp) {
return "";
}
SDPOrigin sdp_get_origin(const std::string sdp) {
SDPOrigin sdp_get_origin(const std::string& sdp) {
SDPOrigin origin;
try {
std::stringstream sstream(sdp);

View File

@ -55,6 +55,6 @@ struct SDPOrigin {
}
};
SDPOrigin sdp_get_origin(const std::string sdp);
SDPOrigin sdp_get_origin(const std::string& sdp);
#endif

24
systemd/daemon.conf Normal file
View File

@ -0,0 +1,24 @@
{
"http_port": 8080,
"rtsp_port": 8854,
"http_base_dir": "/usr/local/share/aes67-daemon/webui/",
"log_severity": 2,
"playout_delay": 0,
"tic_frame_size_at_1fs": 64,
"max_tic_frame_size": 1024,
"sample_rate": 48000,
"rtp_mcast_base": "239.1.0.1",
"rtp_port": 5004,
"ptp_domain": 0,
"ptp_dscp": 48,
"sap_mcast_addr": "239.255.255.255",
"sap_interval": 30,
"syslog_proto": "none",
"syslog_server": "255.255.255.254:1234",
"status_file": "/etc/status.json",
"interface_name": "eth0",
"mdns_enabled": true,
"custom_node_id": "",
"ptp_status_script": "/usr/local/share/aes67-daemon/scripts/ptp_status.sh",
"auto_sinks_update": true
}

24
systemd/install.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
#
# Tested on Ubuntu 21.04
#
#create a user for the daemon
sudo useradd -M -l aes67-daemon -c "AES67 Linux daemon"
#copy the daemon binary
sudo cp ../daemon/aes67-daemon /usr/local/bin/aes67-daemon
#create the daemon webui and scripts directories
sudo install -d -o aes67-daemon /var/lib/aes67-daemon /usr/local/share/aes67-daemon/scripts/ /usr/local/share/aes67-daemon/webui/
#copy the ptp script
sudo install -o aes67-daemon ../daemon/scripts/ptp_status.sh /usr/local/share/aes67-daemon/scripts/
#copy the webui
sudo cp -r ../webui/dist/* /usr/local/share/aes67-daemon/webui/
#copy daemon configuration and status files
sudo install -o aes67-daemon status.json daemon.conf /etc
#copy the daemon systemd service definition
sudo cp aes67-daemon.service /etc/systemd/system
#enable the daemon service
sudo systemctl enable aes67-daemon
sudo systemctl daemon-reexec

4
systemd/status.json Normal file
View File

@ -0,0 +1,4 @@
{
"sources": [ ],
"sinks": [ ]
}

View File

@ -19,4 +19,5 @@ sudo apt-get install -y libasound2-dev
sudo apt-get install -y linuxptp
sudo apt-get install -y libavahi-client-dev
sudo apt install -y linux-headers-$(uname -r)
sudo apt-get install -y libsystemd-dev