22 #include <boost/algorithm/string/trim.hpp>
26 #include "seamless/SeamlessContentStream.h"
29 #include "spdlog/spdlog.h"
30 #include "gmime/gmime.h"
32 #include "cpprest/base_uri.h"
33 #include <gzip/decompress.hpp>
37 const std::string &mcast,
38 unsigned long long tsi, std::string iface,
40 bool seamless_switching,
43 : _cfg(
cfg), _tmgi(std::move(tmgi)), _tsi(tsi), _iface(std::move(iface)), _io_service(io_service), _cache(cache),
44 _flute_thread{}, _seamless(seamless_switching), _get_service(std::move(get_service)),
45 _set_service(std::move(set_service)) {
49 spdlog::info(
"Closing service announcement session with TMGI {}", _tmgi);
50 _flute_receiver.reset();
51 if (_flute_thread.joinable()) {
62 size_t delim = mcast_address.find(
':');
63 if (delim == std::string::npos) {
64 spdlog::error(
"Invalid multicast address {}", mcast_address);
67 _mcast_addr = mcast_address.substr(0, delim);
68 _mcast_port = mcast_address.substr(delim + 1);
69 spdlog::info(
"Starting FLUTE receiver on {}:{} for TSI {}", _mcast_addr, _mcast_port, _tsi);
70 _flute_thread = std::thread{[&]() {
71 _flute_receiver = std::make_unique<LibFlute::Receiver>(_iface, _mcast_addr, atoi(_mcast_port.c_str()), _tsi,
73 _flute_receiver->register_completion_callback(
74 [&](std::shared_ptr<LibFlute::File> file) {
75 spdlog::info(
"{} (TOI {}) has been received",
76 file->meta().content_location, file->meta().toi);
77 if (!_bootstrapped || _toi != file->meta().toi) {
78 _toi = file->meta().toi;
79 if (file->meta().content_type ==
"application/x-gzip") {
80 _raw_content = gzip::decompress(file->buffer(), file->length());
82 _raw_content = std::string(file->buffer());
84 parse_bootstrap(file->buffer());
97 _cfg.lookupValue(
"mw.bootstrap_format", bootstrap_format);
100 _addServiceAnnouncementItems(str);
103 for (
const auto &item: _items) {
105 _handleMbmsEnvelope(item);
110 for (
const auto &item: _items) {
112 _handleMbmbsUserServiceDescriptionBundle(item, bootstrap_format);
123 auto stream = g_mime_stream_mem_new_with_buffer(str.c_str(), str.length());
124 auto parser = g_mime_parser_new_with_stream(stream);
125 g_object_unref(stream);
127 auto mpart = g_mime_parser_construct_part(parser,
nullptr);
128 g_object_unref(parser);
130 auto iter = g_mime_part_iter_new(mpart);
132 GMimeObject *current = g_mime_part_iter_get_current(iter);
133 GMimeObject *parent = g_mime_part_iter_get_parent(iter);
135 if (GMIME_IS_PART (current)) {
136 auto type = std::string(g_mime_content_type_get_mime_type(g_mime_object_get_content_type(current)));
137 std::string location =
"";
138 if (g_mime_object_get_header(current,
"Content-Location")) {
139 location = std::string(g_mime_object_get_header(current,
"Content-Location"));
141 auto options = g_mime_format_options_new();
142 g_mime_format_options_add_hidden_header(
options,
"Content-Type");
143 g_mime_format_options_add_hidden_header(
options,
"Content-Transfer-Encoding");
144 g_mime_format_options_add_hidden_header(
options,
"Content-Location");
145 std::string content = g_mime_object_to_string(current,
options);
146 boost::algorithm::trim_left(content);
148 if (location !=
"") {
149 _items.emplace_back(
Item{
157 }
while (g_mime_part_iter_next(iter));
167 tinyxml2::XMLDocument
doc;
173 for (
auto &ir: _items) {
177 ss_from >> std::get_time(&from,
"%Y-%m-%dT%H:%M:%S.%fZ");
178 ir.valid_from = mktime(&from);
180 struct std::tm until;
181 ss_until >> std::get_time(&until,
"%Y-%m-%dT%H:%M:%S.%fZ");
182 ir.valid_until = mktime(&until);
187 }
catch (std::exception e) {
188 spdlog::warn(
"MBMS envelope parsing failed: {}", e.what());
198 const std::string &bootstrap_format) {
200 tinyxml2::XMLDocument
doc;
210 auto[service, is_new_service] = _registerService(usd, service_id);
214 _handleAppService(app_service, service);
218 _setupBy5GMagConfig(app_service, service, usd);
220 _setupBy5GMagLegacyFormat(app_service, service, usd);
222 _setupByAlternativeContentElement(app_service, service, usd);
225 if (is_new_service && service->content_streams().size() > 0) {
226 _set_service(service_id, service);
229 }
catch (std::exception e) {
230 spdlog::warn(
"MBMS user service desription parsing failed: {}", e.what());
244 bool is_new_service =
false;
245 auto service = _get_service(service_id);
246 if (service ==
nullptr) {
247 service = std::make_shared<Service>(_cache);
248 is_new_service =
true;
255 auto namestr = name->GetText();
256 if (lang && namestr) {
257 service->add_name(namestr, lang);
261 return {service, is_new_service};
273 std::shared_ptr<MBMS_RT::Service> &service) {
279 for (
const auto &item: _items) {
282 web::uri uri(item.uri);
285 const std::string &path = uri.path();
286 size_t spos = path.rfind(
'/');
287 auto base_path = path.substr(0, spos + 1);
290 if (base_path[0] ==
'/') {
291 base_path.erase(0, 1);
293 service->read_master_manifest(item.content, base_path);
294 _base_path = base_path;
300 const std::shared_ptr<MBMS_RT::Service> &service,
301 tinyxml2::XMLElement *usd) {
303 std::vector<std::shared_ptr<ContentStream>> broadcastContentStreams;
304 std::vector<std::shared_ptr<SeamlessContentStream>> unicastContentStreams;
307 delivery_method !=
nullptr;
311 auto manifest_url = std::regex_replace(sdp_uri, std::regex(
".sdp"),
".m3u8");
312 auto broadcast_app_service = delivery_method->FirstChildElement(
315 if (broadcast_app_service !=
nullptr) {
317 base_pattern !=
nullptr;
320 std::string broadcast_url = base_pattern->GetText();
323 std::shared_ptr<ContentStream> cs;
325 cs = std::make_shared<SeamlessContentStream>(broadcast_url, _iface, _io_service, _cache,
326 service->delivery_protocol(), _cfg);
328 cs = std::make_shared<ContentStream>(broadcast_url, _iface, _io_service, _cache, service->delivery_protocol(),
332 for (
const auto &item: _items) {
333 if (item.uri == manifest_url) {
334 cs->read_master_manifest(item.content);
337 item.uri == sdp_uri) {
338 cs->configure_5gbc_delivery_from_sdp(item.content);
342 broadcastContentStreams.push_back(cs);
349 auto unicast_app_service = delivery_method->FirstChildElement(
351 if (unicast_app_service !=
nullptr) {
352 for (
auto *base_pattern = unicast_app_service->FirstChildElement(
354 base_pattern !=
nullptr;
357 std::string unicast_url = base_pattern->GetText();
359 for (
auto *identical_content = app_service->FirstChildElement(
361 identical_content !=
nullptr;
362 identical_content = identical_content->NextSiblingElement(
364 std::shared_ptr<SeamlessContentStream> broadcast_content_stream;
365 bool found_identical_element =
false;
366 for (
auto *base_pattern_ic = identical_content->FirstChildElement(
368 base_pattern_ic !=
nullptr;
371 std::string identical_content_url = base_pattern_ic->GetText();
374 if (unicast_url == identical_content_url) {
375 found_identical_element =
true;
377 for (
auto &element: broadcastContentStreams) {
378 if (element->base() == identical_content_url) {
379 broadcast_content_stream = std::dynamic_pointer_cast<SeamlessContentStream>(element);
385 if (broadcast_content_stream !=
nullptr && found_identical_element) {
386 broadcast_content_stream->set_cdn_endpoint(unicast_url);
388 std::shared_ptr<SeamlessContentStream> cs = std::make_shared<SeamlessContentStream>(manifest_url, _iface,
390 service->delivery_protocol(),
392 cs->set_cdn_endpoint(unicast_url);
393 unicastContentStreams.push_back(cs);
400 for (
auto &element: unicastContentStreams) {
401 service->add_and_start_content_stream(element);
404 for (
auto &element: broadcastContentStreams) {
405 service->add_and_start_content_stream(element);
408 spdlog::info(
"Finished SA setup with 5G-MAG Format");
420 const std::shared_ptr<MBMS_RT::Service> &service,
421 tinyxml2::XMLElement *usd) {
423 std::vector<std::shared_ptr<ContentStream>> broadcastContentStreams;
426 delivery_method !=
nullptr;
431 auto manifest_url = std::regex_replace(sdp_uri, std::regex(
".sdp"),
"." + manifest_type);
432 auto broadcast_app_service = delivery_method->FirstChildElement(
435 if (broadcast_app_service !=
nullptr) {
437 base_pattern !=
nullptr;
440 std::string broadcast_url = base_pattern->GetText();
445 std::shared_ptr<ContentStream> cs;
446 cs = std::make_shared<ContentStream>(broadcast_url, _iface, _io_service, _cache, service->delivery_protocol(),
449 cs->set_base_path(_base_path);
450 for (
const auto &item: _items) {
452 cs->read_master_manifest(item.content);
455 item.uri == sdp_uri) {
456 cs->configure_5gbc_delivery_from_sdp(item.content);
460 broadcastContentStreams.push_back(cs);
465 for (
auto &element: broadcastContentStreams) {
466 service->add_and_start_content_stream(element);
469 spdlog::info(
"Finished SA setup with 5G-MAG Legacy Format");
482 const std::shared_ptr<MBMS_RT::Service> &service,
483 tinyxml2::XMLElement *usd) {
485 if (alternative_content !=
nullptr) {
487 base_pattern !=
nullptr;
489 std::string base = base_pattern->GetText();
492 std::shared_ptr<ContentStream> cs;
494 cs = std::make_shared<SeamlessContentStream>(base, _iface, _io_service, _cache,
495 service->delivery_protocol(), _cfg);
497 cs = std::make_shared<ContentStream>(base, _iface, _io_service, _cache, service->delivery_protocol(),
503 bool broadcast_delivery_available =
false;
505 delivery_method !=
nullptr;
508 auto broadcast_app_service = delivery_method->FirstChildElement(
510 std::string broadcast_base_pattern = broadcast_app_service->FirstChildElement(
513 if (broadcast_base_pattern == base) {
514 for (
const auto &item: _items) {
515 if (item.uri == broadcast_base_pattern) {
516 cs->read_master_manifest(item.content);
519 item.uri == sdp_uri) {
520 broadcast_delivery_available = cs->configure_5gbc_delivery_from_sdp(item.content);
525 bool unicast_delivery_available =
false;
529 if (!broadcast_delivery_available) {
531 std::dynamic_pointer_cast<SeamlessContentStream>(cs)->set_cdn_endpoint(base);
532 unicast_delivery_available =
true;
535 for (
auto *identical_content = app_service->FirstChildElement(
537 identical_content !=
nullptr;
538 identical_content = identical_content->NextSiblingElement(
541 bool base_matched =
false;
542 std::string found_identical_base;
543 for (
auto *base_pattern = identical_content->FirstChildElement(
545 base_pattern !=
nullptr;
547 std::string identical_base = base_pattern->GetText();
548 if (base == identical_base) {
551 found_identical_base = identical_base;
555 if (base_matched && found_identical_base.length()) {
556 std::dynamic_pointer_cast<SeamlessContentStream>(cs)->set_cdn_endpoint(found_identical_base);
562 if (unicast_delivery_available || broadcast_delivery_available) {
563 service->add_and_start_content_stream(cs);
virtual ~ServiceAnnouncement()
void _handleMbmsEnvelope(const Item &item)
Parses the MBMS envelope.
void _addServiceAnnouncementItems(const std::string &str)
Iterates through the service announcement file and adds the different sections/items to the the list ...
void _handleMbmbsUserServiceDescriptionBundle(const Item &item, const std::string &bootstrap_format)
Parses the MBMS USD.
void _handleAppService(tinyxml2::XMLElement *app_service, const std::shared_ptr< Service > &service)
Parse the appService element Spec: Presence of the r12:appService child element of userServiceDescrip...
void _setupBy5GMagConfig(tinyxml2::XMLElement *app_service, const std::shared_ptr< MBMS_RT::Service > &service, tinyxml2::XMLElement *usd)
void _setupBy5GMagLegacyFormat(tinyxml2::XMLElement *app_service, const std::shared_ptr< MBMS_RT::Service > &service, tinyxml2::XMLElement *usd)
Setup according to the format that was used for the first 5G-MAG sample recordings.
std::function< std::shared_ptr< Service >const std::string &service_id)> get_service_callback_t
void start_flute_receiver(const std::string &mcast_address)
Starts the FLUTE receiver at the specified multicast address.
std::tuple< std::shared_ptr< MBMS_RT::Service >, bool > _registerService(tinyxml2::XMLElement *usd, const std::string &service_id)
Creates a new service or finds an existing service for the specified service id.
std::function< void(const std::string &service_id, std::shared_ptr< Service >)> set_service_callback_t
void parse_bootstrap(const std::string &str)
Parse the service announcement/bootstrap file.
void _setupByAlternativeContentElement(tinyxml2::XMLElement *app_service, const std::shared_ptr< MBMS_RT::Service > &service, tinyxml2::XMLElement *usd)
Setup according to original SA format with an alternativeContentElement required to indicate that the...
ServiceAnnouncement(const libconfig::Config &cfg, std::string tmgi, const std::string &mcast, unsigned long long tsi, std::string iface, boost::asio::io_service &io_service, CacheManagement &cache, bool seamless_switching, get_service_callback_t get_service, set_service_callback_t set_service)
static Config cfg
Global configuration object.
static struct argp_option options[]
const std::string MBMS_ENVELOPE
const std::string MBMS_USER_SERVICE_DESCRIPTION
const std::string DASH_MANIFEST
const std::string HLS_MANIFEST
const char *const BASE_PATTERN
const char *const DELIVERY_METHOD
const char *const METADATA_ENVELOPE
const char *const APP_SERVICE_DESCRIPTION_URI
const char *const IDENTICAL_CONTENT
const char *const SERVICE_ID
const char *const UNICAST_APP_SERVICE
const char *const VERSION
const char *const ALTERNATIVE_CONTENT
const char *const BROADCAST_APP_SERVICE
const char *const USER_SERVICE_DESCRIPTION
const char *const SESSION_DESCRIPTION_URI
const char *const VALID_UNTIL
const char *const MIME_TYPE
const char *const VALID_FROM
const char *const APP_SERVICE
const char *const METADATA_URI
const char *const BUNDLE_DESCRIPTION