diff --git a/README.md b/README.md index 693e256..374c133 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ The daemon uses the following open source: * **Merging Technologies ALSA RAVENNA/AES67 Driver** licensed under [GNU GPL](https://www.gnu.org/licenses/gpl-3.0.en.html). * **cpp-httplib** licensed under the [MIT License](https://github.com/yhirose/cpp-httplib/blob/master/LICENSE) +* **Avahi common & client libraries** licensed under the [LGPL License](https://github.com/lathiat/avahi/blob/master/LICENSE) * **Boost libraries** licensed under the [Boost Software License](https://www.boost.org/LICENSE_1_0.txt) ## Repository content ## @@ -21,6 +22,7 @@ The daemon can be cross-compiled for multiple platforms and implements the follo * session handling and SDP parsing and creation * HTTP REST API for control and configuration * SAP discovery protocol and SAP browser +* mDNS sources discovery (using Avahi) and SDP transfer via RTSP * IGMP handling for SAP, RTP and PTP multicast traffic The directory also contains the daemon regression tests in the [tests](daemon/tests) subdirectory. To run daemon tests install the ALSA RAVENNA/AES67 kernel module enter the [tests](daemon/tests) subdirectory and run *./daemon-test -l all* @@ -65,6 +67,7 @@ The daemon and the demo have been tested with **Ubuntu 18.04** distro on **x86/A * node version >= 8.10.0 * npm version >= 3.5.2 * boost libraries version >= 1.65.1 +* Avahi service discovery (if enabled) >= 0.7 The BeagleBone® Black board with ARM Cortex-A8 32-Bit processor was used for testing on ARMv7. See [Ubuntu 18.04 on BeagleBone® Black](https://elinux.org/BeagleBoardUbuntu) for additional information about how to setup Ubuntu on this board. diff --git a/build.sh b/build.sh index 01b95af..eb45a45 100755 --- a/build.sh +++ b/build.sh @@ -40,7 +40,7 @@ cd .. cd daemon echo "Building aes67-daemon ..." -cmake . +cmake -DWITH_AVAHI=ON . make cd .. diff --git a/daemon/.clang-format b/daemon/.clang-format new file mode 100644 index 0000000..d46c7cc --- /dev/null +++ b/daemon/.clang-format @@ -0,0 +1,157 @@ +--- +Language: Cpp +# BasedOnStyle: Chromium +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +... + diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index f184c45..bb1a29b 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -1,12 +1,24 @@ cmake_minimum_required(VERSION 3.7.0) project(aes67-daemon CXX) enable_testing() +option(WITH_AVAHI "Include mDNS support via Avahi" OFF) set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_FLAGS "-g -O3 -DBOOST_LOG_DYN_LINK -DBOOST_LOG_USE_NATIVE_SYSLOG -Wall") +set(CMAKE_CXX_FLAGS "-g -DBOOST_LOG_DYN_LINK -DBOOST_LOG_USE_NATIVE_SYSLOG -Wall") set(RAVENNA_ALSA_LKM "../3rdparty/ravenna-alsa-lkm/") set(CPP_HTTPLIB " ../3rdparty/cpp-httplib/") +find_library(AVAHI_LIBRARY-COMMON NAMES avahi-common) +find_library(AVAHI_LIBRARY-CLIENT NAMES avahi-client) +find_path(AVAHI_INCLUDE_DIR avahi-client/publish.h) +set(AVAHI_LIBRARIES ${AVAHI_LIBRARY-COMMON} ${AVAHI_LIBRARY-CLIENT}) +set(AVAHI_INCLUDE_DIRS ${AVAHI_INCLUDE_DIR}) find_package(Boost COMPONENTS system thread log program_options REQUIRED) include_directories(aes67-daemon ${RAVENNA_ALSA_LKM}/common ${RAVENNA_ALSA_LKM}/driver ${CPP_HTTPLIB} ${Boost_INCLUDE_DIR}) -add_executable(aes67-daemon error_code.cpp json.cpp main.cpp driver_handler.cpp driver_manager.cpp session_manager.cpp http_server.cpp config.cpp interface.cpp log.cpp sap.cpp browser.cpp) +add_executable(aes67-daemon error_code.cpp json.cpp main.cpp driver_handler.cpp driver_manager.cpp session_manager.cpp http_server.cpp config.cpp interface.cpp log.cpp sap.cpp browser.cpp rtsp_client.cpp mdns_client.cpp utils.cpp) add_subdirectory(tests) target_link_libraries(aes67-daemon ${Boost_LIBRARIES}) +if(WITH_AVAHI) + MESSAGE(STATUS "WITH_AVAHI") + add_definitions(-D_USE_AVAHI_) + include_directories(aes67-daemon ${AVAHI_INCLUDE_DIRS}) + target_link_libraries(aes67-daemon ${AVAHI_LIBRARIES}) +endif() diff --git a/daemon/README.md b/daemon/README.md index 9d7b32b..7c5a70a 100644 --- a/daemon/README.md +++ b/daemon/README.md @@ -8,6 +8,7 @@ The daemon is responsible for: * provide an HTTP REST API for the daemon control and configuration * session handling and SDP parsing and creation * SAP discovery protocol and SAP browser +* mDNS sources discovery (using Avahi) and SDP transfer via RTSP * IGMP handling for SAP and RTP sessions ## Configuration file ## @@ -232,6 +233,9 @@ where: > JSON string specifying the IP address of the specified network device. > **_NOTE:_** This parameter is read-only and cannot be set. The server will determine the IP address of the network device at startup time and will monitor it periodically. +> **mdns\_enabled** +> JSON boolean specifying whether the mDNS discovery is enabled or disabled. + ### JSON PTP Config ### Example @@ -383,7 +387,8 @@ where: > JSON boolean specifying whether the source SDP file is fetched from the HTTP URL specified in the **source** parameter or the SDP in the **sdp** parameter is used. > **source** -> JSON string specifying the HTTP URL of the source SDP file. This parameter is mandatory if **use\_sdp** is false. +> JSON string specifying the URL of the source SDP file. At present HTTP and RTSP protocols are supported. +> This parameter is mandatory if **use\_sdp** is false. > **sdp** > JSON string specifying the SDP of the source. This parameter is mandatory if **use\_sdp** is true. diff --git a/daemon/browser.cpp b/daemon/browser.cpp index 4968b05..24d525d 100644 --- a/daemon/browser.cpp +++ b/daemon/browser.cpp @@ -17,11 +17,10 @@ // along with this program. If not, see . // -#include #include "browser.hpp" using namespace std::chrono; -using second_t = std::chrono::duration >; +using second_t = duration >; std::shared_ptr Browser::create( std::shared_ptr config) { @@ -39,7 +38,8 @@ std::shared_ptr Browser::create( std::list Browser::get_remote_sources() { std::list sources_list; std::shared_lock sources_lock(sources_mutex_); - for (auto const& [id, source] : sources_) { + // return list of remote sources ordered by name + for (auto& source: sources_.get()) { sources_list.push_back(source); } return sources_list; @@ -60,9 +60,10 @@ bool Browser::worker() { sap_.set_multicast_interface(config_->get_ip_addr_str()); // Join SAP muticast address igmp_.join(config_->get_ip_addr_str(), config_->get_sap_mcast_addr()); - auto startup = steady_clock::now(); auto sap_timepoint = steady_clock::now(); int sap_interval = 10; + auto mdns_timepoint = steady_clock::now(); + int mdns_interval = 10; while (running_) { bool is_announce; @@ -71,12 +72,14 @@ bool Browser::worker() { std::string sdp; if (sap_.receive(is_announce, msg_id_hash, addr, sdp)) { - char id[13]; - snprintf(id, sizeof id, "%x%x", addr, msg_id_hash); - //BOOST_LOG_TRIVIAL(debug) << "browser:: received SAP message for " << id; + std::stringstream ss; + ss << "sap:" << std::hex << addr << msg_id_hash; + std::string id(ss.str()); + BOOST_LOG_TRIVIAL(debug) << "browser:: received SAP message for " << id; std::unique_lock sources_lock(sources_mutex_); - auto it = sources_.find(id); + + auto it = sources_.get().find(id); if (it == sources_.end()) { // Source is not in the map if (is_announce) { @@ -89,23 +92,25 @@ bool Browser::worker() { source.address = ip::address_v4(ntohl(addr)).to_string(); source.name = sdp_get_subject(sdp); source.last_seen = - duration_cast(steady_clock::now() - startup).count(); + duration_cast(steady_clock::now() - startup_).count(); source.announce_period = 360; //default period - sources_[id] = source; + sources_.insert(source); } } else { // Source is already in the map if (is_announce) { BOOST_LOG_TRIVIAL(debug) - << "browser:: refreshing SAP source " << (*it).second.id; + << "browser:: refreshing SAP source " << it->id; // annoucement, update last seen and announce period + auto upd_source{*it}; uint32_t last_seen = - duration_cast(steady_clock::now() - startup).count(); - (*it).second.announce_period = last_seen - (*it).second.last_seen; - (*it).second.last_seen = last_seen; + duration_cast(steady_clock::now() - startup_).count(); + upd_source.announce_period = last_seen - upd_source.last_seen; + upd_source.last_seen = last_seen; + sources_.replace(it, upd_source); } else { BOOST_LOG_TRIVIAL(info) - << "browser:: removing SAP source " << (*it).second.id; + << "browser:: removing SAP source " << it->id; // deletion, remove entry sources_.erase(it); } @@ -117,22 +122,79 @@ bool Browser::worker() { > sap_interval) { sap_timepoint = steady_clock::now(); // remove all sessions no longer announced - auto offset = duration_cast(steady_clock::now() - startup).count(); + auto offset = duration_cast(steady_clock::now() - startup_).count(); std::unique_lock sources_lock(sources_mutex_); - std::experimental::erase_if(sources_, [offset](auto entry) { - if ((offset - entry.second.last_seen) > - (entry.second.announce_period * 10)) { + for (auto it = sources_.begin(); it != sources_.end();) { + if (it->source == "SAP" && + (offset - it->last_seen) > (it->announce_period * 10)) { // remove from remote SAP sources BOOST_LOG_TRIVIAL(info) - << "browser:: SAP source " << entry.second.id << " timeout"; - return true; + << "browser:: SAP source " << it->id << " timeout"; + it = sources_.erase(it); + } else { + it++; } - return false; - }); + } + } + + // check if it's time to process the mDNS RTSP sources + if ((duration_cast(steady_clock::now() - mdns_timepoint).count()) + > mdns_interval) { + mdns_timepoint = steady_clock::now(); + process_results(); } } return true; } +void Browser::on_new_rtsp_source(const std::string& name, + const std::string& domain, + const RTSPSSource& s) { + uint32_t last_seen = duration_cast(steady_clock::now() - startup_).count(); + std::unique_lock sources_lock(sources_mutex_); + if (sources_.get().find(s.id) == sources_.end()) { + BOOST_LOG_TRIVIAL(info) << "browser:: adding RTSP source " << s.id; + sources_.insert({ s.id, s.source, s.address, name, s.sdp, last_seen, 0 }); + } +} + +void Browser::on_remove_rtsp_source(const std::string& name, + const std::string& domain) { + std::unique_lock sources_lock(sources_mutex_); + auto& name_idx = sources_.get(); + for (auto it = name_idx.find(name); it != name_idx.end(); it++) { + if (it->source == "mDNS") { + BOOST_LOG_TRIVIAL(info) << "browser:: removing RTSP source " << it->id; + name_idx.erase(it); + break; + } + } +} + +bool Browser::init() { + if (!running_) { + /* init mDNS client */ + if (config_->get_mdns_enabled() && !MDNSClient::init()) { + return false; + } + running_ = true; + res_ = std::async(std::launch::async, &Browser::worker, this); + } + return true; +} + +bool Browser::terminate() { + if (running_) { + running_ = false; + /* wait for worker to exit */ + res_.get(); + /* terminate mDNS client */ + if (config_->get_mdns_enabled()) { + MDNSClient::terminate(); + } + } + return true; +} + diff --git a/daemon/browser.hpp b/daemon/browser.hpp index f784220..c593604 100644 --- a/daemon/browser.hpp +++ b/daemon/browser.hpp @@ -26,9 +26,18 @@ #include #include +#include +#include +#include +#include +#include + #include "config.hpp" #include "sap.hpp" #include "igmp.hpp" +#include "mdns_client.hpp" + +using namespace boost::multi_index; struct RemoteSource { std::string id; @@ -40,50 +49,56 @@ struct RemoteSource { uint32_t announce_period; /* period between annoucements */ }; -class Browser { +class Browser : public MDNSClient { public: static std::shared_ptr create( std::shared_ptr config); Browser() = delete; Browser(const Browser&) = delete; Browser& operator=(const Browser&) = delete; - virtual ~Browser(){ stop(); }; + virtual ~Browser(){ terminate(); }; - bool start() { - if (!running_) { - running_ = true; - res_ = std::async(std::launch::async, &Browser::worker, this); - } - return true; - } - - bool stop() { - if (running_) { - running_ = false; - return res_.get(); - } - return true; - } + bool init() override; + bool terminate() override; std::list get_remote_sources(); protected: // singleton, use create() to build - Browser(std::shared_ptr config) - : config_(config){}; + Browser(std::shared_ptr config): + config_(config), + startup_(std::chrono::steady_clock::now()){}; bool worker(); + virtual void on_new_rtsp_source( + const std::string& name, + const std::string& domain, + const RTSPSSource& source) override; + virtual void on_remove_rtsp_source( + const std::string& name, + const std::string& domain) override; + std::shared_ptr config_; std::future res_; std::atomic_bool running_{false}; /* current sources */ - std::map sources_; + struct id_tag{}; + using by_id = hashed_unique, member>; + struct name_tag{}; + using by_name = ordered_non_unique, member>; + using sources_t = multi_index_container>; + + sources_t sources_; mutable std::shared_mutex sources_mutex_; SAP sap_{config_->get_sap_mcast_addr()}; IGMP igmp_; + std::chrono::time_point startup_; }; #endif diff --git a/daemon/config.cpp b/daemon/config.cpp index ae27562..83ecd9f 100644 --- a/daemon/config.cpp +++ b/daemon/config.cpp @@ -65,12 +65,15 @@ std::shared_ptr Config::parse(const std::string& filename) { config.max_tic_frame_size_ = 1024; if (config.sample_rate_ == 0) config.sample_rate_ = 44100; - if (ip::address_v4::from_string(config.rtp_mcast_base_.c_str()).to_ulong() == - INADDR_NONE) + boost::system::error_code ec; + ip::address_v4::from_string(config.rtp_mcast_base_.c_str(), ec); + if (!ec) { config.rtp_mcast_base_ = "239.1.0.1"; - if (ip::address_v4::from_string(config.sap_mcast_addr_.c_str()).to_ulong() == - INADDR_NONE) + } + ip::address_v4::from_string(config.sap_mcast_addr_.c_str(), ec); + if (!ec) { config.sap_mcast_addr_ = "224.2.127.254"; + } if (config.ptp_domain_ > 127) if (config.ptp_domain_ > 127) config.ptp_domain_ = 0; diff --git a/daemon/config.hpp b/daemon/config.hpp index a9ebc71..fbe7c41 100644 --- a/daemon/config.hpp +++ b/daemon/config.hpp @@ -57,6 +57,7 @@ class Config { uint32_t get_ip_addr() const { return ip_addr_; }; const std::string& get_ip_addr_str() const { return ip_str_; }; bool get_need_restart() const { return need_restart_; }; + bool get_mdns_enabled() const { return mdns_enabled; }; void set_http_port(uint16_t http_port) { http_port_ = http_port; }; void set_http_base_dir(const std::string& http_base_dir) { http_base_dir_ = http_base_dir; }; @@ -101,6 +102,9 @@ class Config { void set_mac_addr(const std::array& mac_addr) { mac_addr_ = mac_addr; }; + void set_mdns_enabled(bool enabled) { + mdns_enabled = enabled; + }; private: /* from json */ @@ -121,6 +125,7 @@ class Config { std::string syslog_server_{""}; std::string status_file_{"./status.json"}; std::string interface_name_{"eth0"}; + bool mdns_enabled{true}; /* set during init */ std::array mac_addr_{0, 0, 0, 0, 0, 0}; diff --git a/daemon/daemon.conf b/daemon/daemon.conf index 370318c..0a056e1 100644 --- a/daemon/daemon.conf +++ b/daemon/daemon.conf @@ -15,5 +15,6 @@ "syslog_proto": "none", "syslog_server": "255.255.255.254:1234", "status_file": "./status.json", + "mdns_enabled": true, "interface_name": "lo" } diff --git a/daemon/driver_handler.hpp b/daemon/driver_handler.hpp index df0f65f..b6f5297 100644 --- a/daemon/driver_handler.hpp +++ b/daemon/driver_handler.hpp @@ -44,6 +44,8 @@ class DriverHandler { virtual bool init(const Config& config); virtual bool terminate(); + + protected: virtual void send_command(enum MT_ALSA_msg_id id, size_t size = 0, const uint8_t* data = nullptr); diff --git a/daemon/http_server.cpp b/daemon/http_server.cpp index 5b9bfcd..c62e343 100644 --- a/daemon/http_server.cpp +++ b/daemon/http_server.cpp @@ -77,7 +77,7 @@ static inline void set_error( res.body = message; } -bool HttpServer::start() { +bool HttpServer::init() { /* setup http operations */ if (!svr_.is_valid()) { return false; @@ -325,7 +325,7 @@ bool HttpServer::start() { return retry; } -bool HttpServer::stop() { +bool HttpServer::terminate() { BOOST_LOG_TRIVIAL(info) << "http_server: stopping ... "; svr_.stop(); return res_.get(); diff --git a/daemon/http_server.hpp b/daemon/http_server.hpp index 22db076..13ff59a 100644 --- a/daemon/http_server.hpp +++ b/daemon/http_server.hpp @@ -35,8 +35,8 @@ class HttpServer { : session_manager_(session_manager), browser_(browser), config_(config) {}; - bool start(); - bool stop(); + bool init(); + bool terminate(); private: std::shared_ptr session_manager_; diff --git a/daemon/json.cpp b/daemon/json.cpp index b29eb1d..340b9ce 100644 --- a/daemon/json.cpp +++ b/daemon/json.cpp @@ -91,6 +91,7 @@ std::string config_to_json(const Config& config) { << ",\n \"syslog_server\": \"" << escape_json(config.get_syslog_server()) << "\"" << ",\n \"status_file\": \"" << escape_json(config.get_status_file()) << "\"" << ",\n \"interface_name\": \"" << escape_json(config.get_interface_name()) << "\"" + << ",\n \"mdns_enabled\": \"" << std::boolalpha << config.get_mdns_enabled() << "\"" << ",\n \"mac_addr\": \"" << escape_json(config.get_mac_addr_str()) << "\"" << ",\n \"ip_addr\": \"" << escape_json(config.get_ip_addr_str()) << "\"" << "\n}\n"; @@ -295,6 +296,8 @@ Config json_to_config_(std::istream& js, Config& config) { config.set_syslog_proto(remove_undesired_chars(val.get_value())); } else if (key == "syslog_server") { config.set_syslog_server(remove_undesired_chars(val.get_value())); + } else if (key == "mdns_enabled") { + config.set_mdns_enabled(val.get_value()); } else if (key == "mac_addr" || key == "ip_addr") { /* ignored */ } else { diff --git a/daemon/main.cpp b/daemon/main.cpp index 9f91f50..0f7a0ff 100644 --- a/daemon/main.cpp +++ b/daemon/main.cpp @@ -111,22 +111,22 @@ int main(int argc, char* argv[]) { /* start session manager */ auto session_manager = SessionManager::create(driver, config); - if (session_manager == nullptr || !session_manager->start()) { + if (session_manager == nullptr || !session_manager->init()) { throw std::runtime_error( - std::string("SessionManager:: start failed")); + std::string("SessionManager:: init failed")); } - /* start browser */ + /* start browser */ auto browser = Browser::create(config); - if (browser == nullptr || !browser->start()) { + if (browser == nullptr || !browser->init()) { throw std::runtime_error( - std::string("Browser:: start failed")); + std::string("Browser:: init failed")); } /* start http server */ HttpServer http_server(session_manager, browser, config); - if (!http_server.start()) { - throw std::runtime_error(std::string("HttpServer:: start failed")); + if (!http_server.init()) { + throw std::runtime_error(std::string("HttpServer:: init failed")); } /* load session status from file */ @@ -154,21 +154,21 @@ int main(int argc, char* argv[]) { session_manager->save_status(); /* stop http server */ - if (!http_server.stop()) { + if (!http_server.terminate()) { throw std::runtime_error( - std::string("HttpServer:: stop failed")); + std::string("HttpServer:: terminate failed")); } /* stop browser */ - if (!browser->stop()) { + if (!browser->terminate()) { throw std::runtime_error( - std::string("Browser:: stop failed")); + std::string("Browser:: terminate failed")); } /* stop session manager */ - if (!session_manager->stop()) { + if (!session_manager->terminate()) { throw std::runtime_error( - std::string("SessionManager:: stop failed")); + std::string("SessionManager:: terminate failed")); } /* stop driver manager */ diff --git a/daemon/mdns_client.cpp b/daemon/mdns_client.cpp new file mode 100644 index 0000000..cd577c6 --- /dev/null +++ b/daemon/mdns_client.cpp @@ -0,0 +1,255 @@ +// +// mdns_client.cpp +// +// Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "mdns_client.hpp" + +#include + +#include "config.hpp" +#include "log.hpp" +#include "rtsp_client.hpp" + +#ifdef _USE_AVAHI_ +void MDNSClient::resolve_callback(AvahiServiceResolver* r, + AvahiIfIndex interface, + 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, + void* userdata) { + MDNSClient& mdns = *(reinterpret_cast(userdata)); + /* Called whenever a service has been resolved successfully or timed out */ + switch (event) { + case AVAHI_RESOLVER_FAILURE: + BOOST_LOG_TRIVIAL(error) << "avahi_client:: (Resolver) failed to resolve " + << "service " << name << " of type " << type + << " in domain " << domain << " : " + << avahi_strerror(avahi_client_errno( + avahi_service_resolver_get_client(r))); + break; + + case AVAHI_RESOLVER_FOUND: + BOOST_LOG_TRIVIAL(debug) << "avahi_client:: (Resolver) " + << "service " << name << " of type " << type + << " in domain " << domain; + + char addr[AVAHI_ADDRESS_STR_MAX]; + avahi_address_snprint(addr, sizeof(addr), address); + + char info[256]; + snprintf(info, sizeof(info), + "%s:%u (%s), " + "local: %i, " + "our_own: %i, " + "wide_area: %i, " + "multicast: %i, " + "cached: %i", + host_name, port, addr, !!(flags & AVAHI_LOOKUP_RESULT_LOCAL), + !!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN), + !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA), + !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST), + !!(flags & AVAHI_LOOKUP_RESULT_CACHED)); + BOOST_LOG_TRIVIAL(debug) << "avahi_client:: (Resolver) " << info; + + boost::system::error_code ec; + boost::asio::ip::address_v4::from_string(addr, ec); + if (!ec) { + /* if valid IPv4 address retrieve source data via RTSP */ + std::lock_guard lock(mdns.sources_res_mutex_); + + /* have fun ;-) */ + mdns.sources_res_.emplace_back(std::async( + std::launch::async, + [&mdns, name_ = std::forward(name), + domain_ = std::forward(domain), + addr_ = std::forward(addr), + port_ = std::forward(std::to_string(port))] { + auto res = RTSPClient::describe(std::string("/by-name/") + name_, + addr_, port_); + if (res.first) { + mdns.on_new_rtsp_source(name_, domain_, res.second); + } + })); + } + + break; + } + + avahi_service_resolver_free(r); +} + +void MDNSClient::browse_callback(AvahiServiceBrowser* b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char* name, + const char* type, + const char* domain, + AvahiLookupResultFlags flags, + void* userdata) { + MDNSClient& mdns = *(reinterpret_cast(userdata)); + /* Called whenever a new services becomes available on the LAN or is removed + * from the LAN */ + switch (event) { + case AVAHI_BROWSER_FAILURE: + BOOST_LOG_TRIVIAL(fatal) << "avahi_client:: (Browser) " + << avahi_strerror(avahi_client_errno( + avahi_service_browser_get_client(b))); + avahi_threaded_poll_quit(mdns.poll_.get()); + return; + + case AVAHI_BROWSER_NEW: + BOOST_LOG_TRIVIAL(info) << "avahi_client:: (Browser) NEW: " + << "service " << name << " of type " << type + << " in domain " << domain; + /* We ignore the returned resolver object. In the callback + function we free it. If the server is terminated before + the callback function is called the server will free + the resolver for us. */ + if (!(avahi_service_resolver_new(mdns.client_.get(), interface, protocol, + name, type, domain, AVAHI_PROTO_UNSPEC, + AVAHI_LOOKUP_NO_TXT, resolve_callback, + &mdns))) { + BOOST_LOG_TRIVIAL(error) + << "avahi_client:: " + << "Failed to resolve service " << name << " : " + << avahi_strerror(avahi_client_errno(mdns.client_.get())); + } + break; + + case AVAHI_BROWSER_REMOVE: + BOOST_LOG_TRIVIAL(info) << "avahi_client:: (Browser) REMOVE: " + << "service " << name << " of type " << type + << " in domain " << domain; + mdns.on_remove_rtsp_source(name, domain); + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + BOOST_LOG_TRIVIAL(debug) << "avahi_client:: (Browser) ALL_FOR_NOW"; + break; + + case AVAHI_BROWSER_CACHE_EXHAUSTED: + BOOST_LOG_TRIVIAL(debug) << "avahi_client:: (Browser) CACHE_EXHAUSTED"; + break; + } +} + +void MDNSClient::client_callback(AvahiClient* c, + AvahiClientState state, + void* userdata) { + MDNSClient& mdns = *(reinterpret_cast(userdata)); + /* Called whenever the client or server state changes */ + if (state == AVAHI_CLIENT_FAILURE) { + BOOST_LOG_TRIVIAL(fatal) << "avahi_client:: server connection failure: " + << avahi_strerror(avahi_client_errno(c)); + avahi_threaded_poll_quit(mdns.poll_.get()); + } +} +#endif + +bool MDNSClient::init() { + if (running_) { + return true; + } + +#ifdef _USE_AVAHI_ + /* allocate poll loop object */ + poll_.reset(avahi_threaded_poll_new()); + if (poll_ == nullptr) { + BOOST_LOG_TRIVIAL(fatal) + << "avahi_client:: failed to create threaded poll object"; + return false; + } + + /* allocate a new client */ + int error; + client_.reset(avahi_client_new(avahi_threaded_poll_get(poll_.get()), + AVAHI_CLIENT_NO_FAIL, client_callback, this, + &error)); + if (client_ == nullptr) { + BOOST_LOG_TRIVIAL(fatal) + << "avahi_client:: failed to create client: " << avahi_strerror(error); + return false; + } + + /* Create the service browser */ + sb_.reset(avahi_service_browser_new(client_.get(), AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, "_rtsp._tcp", nullptr, + {}, browse_callback, this)); + if (sb_ == nullptr) { + BOOST_LOG_TRIVIAL(fatal) + << "avahi_client:: failed to create service browser: " + << avahi_strerror(avahi_client_errno(client_.get())); + return false; + } + + (void)avahi_threaded_poll_start(poll_.get()); +#endif + running_ = true; + return true; +} + +void MDNSClient::process_results() { +#ifdef _USE_AVAHI_ + std::lock_guard lock(sources_res_mutex_); + /* remove all completed results and populate remote sources list */ + sources_res_.remove_if([](auto& result) { + if (!result.valid()) { + /* if invalid future remove from the list */ + return true; + } + auto status = result.wait_for(std::chrono::milliseconds(0)); + if (status == std::future_status::ready) { + result.get(); + /* if completed remove from the list */ + return true; + } + /* if not completed leave in the list */ + return false; + }); +#endif +} + +bool MDNSClient::terminate() { + if (running_) { + running_ = false; +#ifdef _USE_AVAHI_ + /* remove all completed results and populate remote sources list */ + /* wait for all pending results and remove from list */ + std::lock_guard lock(sources_res_mutex_); + BOOST_LOG_TRIVIAL(fatal) << "avahi_client:: waiting for " + << sources_res_.size() << " RTSP clients"; + sources_res_.remove_if([](auto& result) { + if (result.valid()) { + result.wait(); + } + return true; + }); + + avahi_threaded_poll_stop(poll_.get()); +#endif + } + return true; +} diff --git a/daemon/mdns_client.hpp b/daemon/mdns_client.hpp new file mode 100644 index 0000000..28de978 --- /dev/null +++ b/daemon/mdns_client.hpp @@ -0,0 +1,99 @@ +// +// mdns_client.hpp +// +// Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef _AVAHI_CLIENT_HPP_ +#define _AVAHI_CLIENT_HPP_ + +#ifdef _USE_AVAHI_ +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include "config.hpp" +#include "rtsp_client.hpp" + +class MDNSClient { + public: + MDNSClient(){}; + MDNSClient(const MDNSClient&) = delete; + MDNSClient& operator=(const MDNSClient&) = delete; + virtual ~MDNSClient() { terminate(); }; + + virtual bool init(); + virtual bool terminate(); + + protected: + virtual void on_new_rtsp_source(const std::string& name, + const std::string& domain, + const RTSPSSource& source) = 0; + virtual void on_remove_rtsp_source(const std::string& name, + const std::string& domain) = 0; + + void process_results(); + std::list > sources_res_; + std::mutex sources_res_mutex_; + + std::atomic_bool running_{false}; + +#ifdef _USE_AVAHI_ + /* order is important here */ + std::unique_ptr poll_{ + nullptr, &avahi_threaded_poll_free}; + std::unique_ptr< ::AvahiClient, decltype(&avahi_client_free)> client_{ + nullptr, &avahi_client_free}; + std::unique_ptr + sb_{nullptr, &avahi_service_browser_free}; + + static void resolve_callback(AvahiServiceResolver* r, + AvahiIfIndex interface, + 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, + void* userdata); + static void browse_callback(AvahiServiceBrowser* b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char* name, + const char* type, + const char* domain, + AvahiLookupResultFlags flags, + void* userdata); + static void client_callback(AvahiClient* c, + AvahiClientState state, + void* userdata); +#endif +}; + +#endif diff --git a/daemon/rtsp_client.cpp b/daemon/rtsp_client.cpp new file mode 100644 index 0000000..f6a75ee --- /dev/null +++ b/daemon/rtsp_client.cpp @@ -0,0 +1,177 @@ +// +// rtsp_client.cpp +// +// Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.hpp" +#include "utils.hpp" +#include "rtsp_client.hpp" + +using namespace boost::asio; +using namespace boost::asio::ip; +using namespace boost::algorithm; + +struct RtspResponse { + uint32_t cseq; + std::string content_type; + uint64_t content_length; + std::string body; +}; + +RtspResponse read_response(tcp::iostream& s, uint16_t max_length) { + RtspResponse res; + std::string header; + /* + RTSP/1.0 200 OK + CSeq: 312 + Date: 23 Jan 1997 15:35:06 GMT + Content-Type: application/sdp + Content-Length: 376 + + */ + + try { + while (std::getline(s, header) && header != "" && header != "\r") { + to_lower(header); + trim(header); + if (header.rfind("cseq:", 0) != std::string::npos) { + res.cseq = std::stoi(header.substr(5)); + } else if (header.rfind("content-type:", 0) != std::string::npos) { + res.content_type = header.substr(13); + trim(res.content_type); + } else if (header.rfind("content-length:", 0) != std::string::npos) { + res.content_length = std::stoi(header.substr(15)); + } + } + } catch (...) { + BOOST_LOG_TRIVIAL(error) << "rtsp_client:: invalid response header, " + << "cannot perform number conversion"; + } + + // read up to max_length + if (res.content_length > 0 && res.content_length < max_length) { + res.body.reserve(res.content_length); + std::copy_n(std::istreambuf_iterator(s), res.content_length, + std::back_inserter(res.body)); + } + + return res; +} + +std::pair RTSPClient::describe(const std::string& path, + const std::string& address, + const std::string& port) { + RTSPSSource rs; + bool success{false}; + try { + tcp::iostream s; + +#if BOOST_VERSION < 106700 + s.expires_from_now(boost::posix_time::seconds(client_timeout)); +#else + s.expires_from_now(std::chrono::seconds(client_timeout)); +#endif + + BOOST_LOG_TRIVIAL(debug) << "rtsp_client:: connecting to " + << "rtsp://" << address << ":" << port << path; + s.connect(address, port.length() ? port : dft_port); + if (!s) { + BOOST_LOG_TRIVIAL(warning) + << "rtsp_client:: unable to connect to " << address << ":" << port; + return std::make_pair(success, rs); + } + + uint16_t cseq = seq_number++; + s << "DESCRIBE rtsp://" << address << ":" << port + << httplib::detail::encode_url(path) << " RTSP/1.0\r\n"; + s << "CSeq: " << cseq << "\r\n"; + s << "User-Agent: aes67-daemon\r\n"; + s << "Accept: application/sdp\r\n\r\n"; + + // By default, the stream is tied with itself. This means that the stream + // automatically flush the buffered output before attempting a read. It is + // not necessary not explicitly flush the stream at this point. + + // Check that response is OK. + std::string rtsp_version; + s >> rtsp_version; + unsigned int status_code; + s >> status_code; + std::string status_message; + std::getline(s, status_message); + + if (!s || rtsp_version.substr(0, 5) != "RTSP/") { + BOOST_LOG_TRIVIAL(error) << "rtsp_client:: invalid response from " + << "rtsp://" << address << ":" << port << path; + return std::make_pair(success, rs); + } + + if (status_code != 200) { + BOOST_LOG_TRIVIAL(error) << "rtsp_client:: response with status code " + << status_code << " from " + << "rtsp://" << address << ":" << port << path; + return std::make_pair(success, rs); + } + + auto res = read_response(s, max_body_length); + if (res.content_type.rfind("application/sdp", 0) == std::string::npos) { + BOOST_LOG_TRIVIAL(error) << "rtsp_client:: unsupported content-type " + << res.content_type << " from " + << "rtsp://" << address << ":" << port << path; + return std::make_pair(success, rs); + } + if (res.cseq != cseq) { + BOOST_LOG_TRIVIAL(error) + << "rtsp_client:: invalid response sequence " << res.cseq << " from " + << "rtsp://" << address << ":" << port << path; + return std::make_pair(success, rs); + } + + std::stringstream ss; + ss << "rtsp:" << std::hex + << crc16(reinterpret_cast(res.body.c_str()), + res.body.length()) + << std::hex << ip::address_v4::from_string(address.c_str()).to_ulong(); + + rs.id = ss.str(); + rs.source = "mDNS"; + rs.address = address; + rs.sdp = std::move(res.body); + + BOOST_LOG_TRIVIAL(info) << "rtsp_client:: describe completed " + << "rtsp://" << address << ":" << port << path; + + success = true; + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) + << "rtsp_client:: error with " + << "rtsp://" << address << ":" << port << path << ": " << e.what(); + } + + return std::make_pair(success, rs); +} diff --git a/daemon/rtsp_client.hpp b/daemon/rtsp_client.hpp new file mode 100644 index 0000000..60b2561 --- /dev/null +++ b/daemon/rtsp_client.hpp @@ -0,0 +1,44 @@ +// +// rtsp_include.hpp +// +// Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef _RTSP_HPP_ +#define _RTSP_HPP_ + +struct RTSPSSource { + std::string id; + std::string source; + std::string address; + std::string sdp; +}; + +class RTSPClient { + public: + constexpr static uint16_t max_body_length = 4096; // byte + constexpr static uint16_t client_timeout = 10; // sec + constexpr static const char dft_port[] = "554"; + + static std::pair describe( + const std::string& path, + const std::string& address, + const std::string& port = dft_port); + + inline static std::atomic seq_number; +}; + +#endif diff --git a/daemon/sap.cpp b/daemon/sap.cpp index eb542af..89203f8 100644 --- a/daemon/sap.cpp +++ b/daemon/sap.cpp @@ -99,7 +99,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 < length; i++) { + for (int i = 8; buffer[i] != 0 && i < static_cast(length); i++) { buffer[i] = std::tolower(buffer[i]); } if (!memcmp(buffer + 8, "application/sdp", 16)) { diff --git a/daemon/session_manager.cpp b/daemon/session_manager.cpp index 8f3b3a4..e93ca08 100644 --- a/daemon/session_manager.cpp +++ b/daemon/session_manager.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,8 @@ #include "json.hpp" #include "log.hpp" #include "session_manager.hpp" +#include "utils.hpp" +#include "rtsp_client.hpp" static uint8_t get_codec_word_lenght(const std::string& codec) { @@ -122,6 +125,7 @@ bool SessionManager::parse_sdp(const std::string sdp, StreamInfo& info) const { std::stringstream ssstrem(sdp); std::string line; while (getline(ssstrem, line, '\n')) { + boost::trim(line); ++num; if (line[1] != '=') { BOOST_LOG_TRIVIAL(error) @@ -289,7 +293,7 @@ bool SessionManager::parse_sdp(const std::string sdp, StreamInfo& info) const { } } catch (...) { BOOST_LOG_TRIVIAL(fatal) << "session_manager:: invalid SDP at line " << num - << ", cannot perform number convesrion"; + << ", cannot perform number conversion"; return false; } @@ -659,45 +663,55 @@ std::error_code SessionManager::add_sink(const StreamSink& sink) { return DaemonErrc::invalid_url; } - if (!boost::iequals(protocol, "http")) { - BOOST_LOG_TRIVIAL(error) - << "session_manager:: unsupported protocol in URL " << sink.source; - return DaemonErrc::invalid_url; - } - - httplib::Client cli(host.c_str(), - !atoi(port.c_str()) ? 80 : atoi(port.c_str())); - cli.set_timeout_sec(10); - auto res = cli.Get(path.c_str()); - if (!res) { - BOOST_LOG_TRIVIAL(error) - << "session_manager:: cannot retrieve SDP from URL " << sink.source; - return DaemonErrc::cannot_retrieve_sdp; - } - - if (res->status != 200) { - BOOST_LOG_TRIVIAL(error) - << "session_manager:: cannot retrieve SDP from URL " << sink.source - << " server reply " << res->status; - return DaemonErrc::cannot_retrieve_sdp; + std::string sdp; + if (boost::iequals(protocol, "http")) { + httplib::Client cli(host.c_str(), + !atoi(port.c_str()) ? 80 : atoi(port.c_str())); + cli.set_timeout_sec(10); + auto res = cli.Get(path.c_str()); + if (!res) { + BOOST_LOG_TRIVIAL(error) + << "session_manager:: annot retrieve SDP from URL " << sink.source; + return DaemonErrc::cannot_retrieve_sdp; + } + if (res->status != 200) { + BOOST_LOG_TRIVIAL(error) + << "session_manager:: cannot retrieve SDP from URL " << sink.source + << " server reply " << res->status; + return DaemonErrc::cannot_retrieve_sdp; + } + sdp = std::move(res->body); + } else if (boost::iequals(protocol, "rtsp")) { + auto res = RTSPClient::describe(path, host, port); + if (!res.first) { + BOOST_LOG_TRIVIAL(error) + << "session_manager:: cannot retrieve SDP from URL " << sink.source; + return DaemonErrc::cannot_retrieve_sdp; + } + sdp = std::move(res.second.sdp); + } else { + BOOST_LOG_TRIVIAL(error) + << "session_manager:: unsupported protocol in URL " << sink.source; + return DaemonErrc::invalid_url; } BOOST_LOG_TRIVIAL(info) << "session_manager:: SDP from URL " << sink.source << " :\n" - << res->body; + << sdp; - if (!parse_sdp(res->body, info)) { + if (!parse_sdp(sdp, info)) { return DaemonErrc::cannot_parse_sdp; } - info.sink_sdp = res->body; + info.sink_sdp = std::move(sdp); } else { - BOOST_LOG_TRIVIAL(info) << "session_manager:: using SDP " << sink.sdp; + BOOST_LOG_TRIVIAL(info) << "session_manager:: using SDP " + << std::endl << sink.sdp; if (!parse_sdp(sink.sdp, info)) { return DaemonErrc::cannot_parse_sdp; } - info.sink_sdp = sink.sdp; + info.sink_sdp = std::move(sink.sdp); } info.sink_source = sink.source; info.sink_use_sdp = true; // save back and use with SDP file @@ -841,21 +855,6 @@ void SessionManager::get_ptp_status(PTPStatus& status) const { status = ptp_status_; } -static uint16_t crc16(const uint8_t* p, size_t len) { - uint8_t x; - uint16_t crc = 0xFFFF; - - while (len--) { - x = crc >> 8 ^ *p++; - x ^= x >> 4; - crc = (crc << 8) ^ - (static_cast(x << 12)) ^ - (static_cast(x << 5)) ^ - (static_cast(x)); - } - return crc; -} - size_t SessionManager::process_sap() { size_t sdp_len_sum = 0; // set to contain sources currently announced diff --git a/daemon/session_manager.hpp b/daemon/session_manager.hpp index beb3b98..5a1c1b6 100644 --- a/daemon/session_manager.hpp +++ b/daemon/session_manager.hpp @@ -101,10 +101,10 @@ class SessionManager { SessionManager() = delete; SessionManager(const SessionManager&) = delete; SessionManager& operator=(const SessionManager&) = delete; - virtual ~SessionManager(){ stop(); }; + virtual ~SessionManager(){ terminate(); }; // session manager interface - bool start() { + bool init() { if (!running_) { running_ = true; res_ = std::async(std::launch::async, &SessionManager::worker, this); @@ -112,7 +112,7 @@ class SessionManager { return true; } - bool stop() { + bool terminate() { if (running_) { running_ = false; auto ret = res_.get(); diff --git a/daemon/tests/daemon.conf b/daemon/tests/daemon.conf index 18f8c51..609897c 100644 --- a/daemon/tests/daemon.conf +++ b/daemon/tests/daemon.conf @@ -6,7 +6,7 @@ "tic_frame_size_at_1fs": 192, "max_tic_frame_size": 1024, "sample_rate": 44100, - "rtp_mcast_base": "239.2.0.1", + "rtp_mcast_base": "239.1.0.1", "rtp_port": 6004, "ptp_domain": 0, "ptp_dscp": 46, @@ -16,6 +16,7 @@ "syslog_server": "255.255.255.254:1234", "status_file": "", "interface_name": "lo", + "mdns_enabled": "false", "mac_addr": "00:00:00:00:00:00", "ip_addr": "127.0.0.1" } diff --git a/daemon/tests/daemon_test.cpp b/daemon/tests/daemon_test.cpp index f47234c..3cd4cf8 100644 --- a/daemon/tests/daemon_test.cpp +++ b/daemon/tests/daemon_test.cpp @@ -333,7 +333,7 @@ BOOST_AUTO_TEST_CASE(get_config) { BOOST_CHECK_MESSAGE(tic_frame_size_at_1fs == 192, "config as excepcted"); BOOST_CHECK_MESSAGE(max_tic_frame_size == 1024, "config as excepcted"); BOOST_CHECK_MESSAGE(sample_rate == 44100, "config as excepcted"); - BOOST_CHECK_MESSAGE(rtp_mcast_base == "239.2.0.1", "config as excepcted"); + BOOST_CHECK_MESSAGE(rtp_mcast_base == "239.1.0.1", "config as excepcted"); BOOST_CHECK_MESSAGE(rtp_port == 6004, "config as excepcted"); BOOST_CHECK_MESSAGE(ptp_domain == 0, "config as excepcted"); BOOST_CHECK_MESSAGE(ptp_dscp == 46, "config as excepcted"); diff --git a/daemon/utils.cpp b/daemon/utils.cpp new file mode 100644 index 0000000..46bbd65 --- /dev/null +++ b/daemon/utils.cpp @@ -0,0 +1,34 @@ +// +// utils.cpp +// +// Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// + +#include "utils.hpp" + +uint16_t crc16(const uint8_t* p, size_t len) { + uint8_t x; + uint16_t crc = 0xFFFF; + + while (len--) { + x = crc >> 8 ^ *p++; + x ^= x >> 4; + crc = (crc << 8) ^ (static_cast(x << 12)) ^ + (static_cast(x << 5)) ^ (static_cast(x)); + } + return crc; +} diff --git a/daemon/utils.hpp b/daemon/utils.hpp new file mode 100644 index 0000000..07345f7 --- /dev/null +++ b/daemon/utils.hpp @@ -0,0 +1,29 @@ +// +// utils.hpp +// +// Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef _UTILS_HPP_ +#define _UTILS_HPP_ + +#include + +#include + +uint16_t crc16(const uint8_t* p, size_t len); + +#endif diff --git a/ubuntu-packages.sh b/ubuntu-packages.sh index d6b8310..964e9ff 100755 --- a/ubuntu-packages.sh +++ b/ubuntu-packages.sh @@ -14,5 +14,6 @@ sudo apt-get install -y libboost-all-dev sudo apt-get install -y valgrind sudo apt-get install -y linux-sound-base alsa-base alsa-utils sudo apt-get install -y linuxptp +sudo apt-get install -y libavahi-client-dev sudo apt install -y linux-headers-$(uname -r) diff --git a/webui/src/RemoteSources.js b/webui/src/RemoteSources.js index 7ebc232..38274e1 100644 --- a/webui/src/RemoteSources.js +++ b/webui/src/RemoteSources.js @@ -68,7 +68,7 @@ class RemoteSourceEntry extends Component { - + ); @@ -96,7 +96,7 @@ class RemoteSourceList extends Component { Name RTP Address Port - Last seen + Seen Period : @@ -184,7 +184,7 @@ class RemoteSources extends Component { closeInfo={this.closeInfo} infoTitle={this.state.infoTitle} id={this.state.source.id} - address={this.state.source.address} + source={this.state.source.source} name={this.state.source.name} sdp={this.state.source.sdp} /> : undefined } diff --git a/webui/src/SinkEdit.js b/webui/src/SinkEdit.js index 01ea4ad..a860ba6 100644 --- a/webui/src/SinkEdit.js +++ b/webui/src/SinkEdit.js @@ -51,6 +51,7 @@ class SinkEdit extends Component { constructor(props) { super(props); this.state = { + sources: [], id: this.props.sink.id, name: this.props.sink.name, nameErr: false, @@ -75,10 +76,20 @@ class SinkEdit extends Component { this.onChangeChannels = this.onChangeChannels.bind(this); this.onChangeChannelsMap = this.onChangeChannelsMap.bind(this); this.inputIsValid = this.inputIsValid.bind(this); + this.fetchRemoteSources = this.fetchRemoteSources.bind(this); + this.onChangeRemoteSourceSDP = this.onChangeRemoteSourceSDP.bind(this); + } + + fetchRemoteSources() { + RestAPI.getRemoteSources() + .then(response => response.json()) + .then( + data => this.setState( { sources: data.remote_sources })) } componentDidMount() { Modal.setAppElement('body'); + this.fetchRemoteSources(); } addSink(message) { @@ -130,6 +141,12 @@ class SinkEdit extends Component { this.setState({ map: map }); } + onChangeRemoteSourceSDP(e) { + if (e.target.value) { + this.setState({ sdp: e.target.value }); + } + } + inputIsValid() { return !this.state.nameErr && !this.state.sourceErr && @@ -160,11 +177,22 @@ class SinkEdit extends Component { this.setState({useSdp: e.target.checked})} /> - + Source URL this.setState({source: e.target.value, sourceErr: !e.currentTarget.checkValidity()})} disabled={this.state.useSdp ? true : undefined} required/> - SDP + Remote Source SDP + + + + + + SDP