X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=shell%2Fserver.cpp;h=87b7e105c3303aa203e0fd4f10c1c117a2012554;hb=ff8eb9202aef3824d41592c53c9eacedf462f920;hp=b66295dd287a88a7a81f6da0e31a93c8969d00c7;hpb=dfd1f0521cc19463cae95433ba97d41a5b949059;p=casparcg diff --git a/shell/server.cpp b/shell/server.cpp index b66295dd2..87b7e105c 100644 --- a/shell/server.cpp +++ b/shell/server.cpp @@ -1,225 +1,490 @@ -/* -* Copyright (c) 2011 Sveriges Television AB -* -* This file is part of CasparCG (www.casparcg.com). -* -* CasparCG 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 -* (at your option) any later version. -* -* CasparCG 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 CasparCG. If not, see . -* -* Author: Robert Nagy, ronag89@gmail.com -*/ -#include "stdafx.h" - -#include "server.h" - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace caspar { - -using namespace core; -using namespace protocol; - -struct server::impl : boost::noncopyable -{ - spl::shared_ptr event_subject_; - std::unique_ptr accel_factory_; - std::vector> async_servers_; - std::vector> channels_; - - impl() - { - auto accel_str = env::properties().get(L"configuration.accelerator", L"auto"); - - if(accel_str == L"cpu") - accel_factory_.reset(new accelerator::cpu::factory()); - else - { - try - { - accel_factory_.reset(new accelerator::ogl::factory()); - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - accel_factory_.reset(new accelerator::cpu::factory()); - CASPAR_LOG(warning) << L"Using fallback CPU mixer."; - } - } - - ffmpeg::init(); - CASPAR_LOG(info) << L"Initialized ffmpeg module."; - - bluefish::init(); - CASPAR_LOG(info) << L"Initialized bluefish module."; - - decklink::init(); - CASPAR_LOG(info) << L"Initialized decklink module."; - - oal::init(); - CASPAR_LOG(info) << L"Initialized oal module."; - - screen::init(); - CASPAR_LOG(info) << L"Initialized ogl module."; - - image::init(); - CASPAR_LOG(info) << L"Initialized image module."; - - flash::init(); - CASPAR_LOG(info) << L"Initialized flash module."; - - setup_channels(env::properties()); - CASPAR_LOG(info) << L"Initialized channels."; - - setup_controllers(env::properties()); - CASPAR_LOG(info) << L"Initialized controllers."; - } - - ~impl() - { - image::uninit(); - ffmpeg::uninit(); - - async_servers_.clear(); - channels_.clear(); - } - - void setup_channels(const boost::property_tree::wptree& pt) - { - using boost::property_tree::wptree; - BOOST_FOREACH(auto& xml_channel, pt.get_child(L"configuration.channels")) - { - auto format_desc = video_format_desc(xml_channel.second.get(L"video-mode", L"PAL")); - if(format_desc.format == video_format::invalid) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Invalid video-mode.")); - - auto channel = spl::make_shared(static_cast(channels_.size()+1), format_desc, accel_factory_->create_image_mixer()); - - BOOST_FOREACH(auto& xml_consumer, xml_channel.second.get_child(L"consumers")) - { - try - { - auto name = xml_consumer.first; - if(name == L"screen") - channel->output()->add(caspar::screen::create_consumer(xml_consumer.second)); - else if(name == L"bluefish") - channel->output()->add(bluefish::create_consumer(xml_consumer.second)); - else if(name == L"decklink") - channel->output()->add(decklink::create_consumer(xml_consumer.second)); - else if(name == L"file") - channel->output()->add(ffmpeg::create_consumer(xml_consumer.second)); - else if(name == L"system-audio") - channel->output()->add(oal::create_consumer()); - else if(name != L"") - CASPAR_LOG(warning) << "Invalid consumer: " << name; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - } - } - - channel->subscribe(monitor::observable::observer_ptr(event_subject_)); - channels_.push_back(channel); - } - - // Dummy diagnostics channel - if(env::properties().get(L"configuration.channel-grid", false)) - channels_.push_back(spl::make_shared(static_cast(channels_.size()+1), core::video_format_desc(core::video_format::x576p2500), accel_factory_->create_image_mixer())); - } - - void setup_controllers(const boost::property_tree::wptree& pt) - { - using boost::property_tree::wptree; - BOOST_FOREACH(auto& xml_controller, pt.get_child(L"configuration.controllers")) - { - try - { - auto name = xml_controller.first; - auto protocol = xml_controller.second.get(L"protocol"); - - if(name == L"tcp") - { - unsigned int port = xml_controller.second.get(L"port", 5250); - auto asyncbootstrapper = spl::make_shared(create_protocol(protocol), port); - asyncbootstrapper->Start(); - async_servers_.push_back(asyncbootstrapper); - } - else - CASPAR_LOG(warning) << "Invalid controller: " << name; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - } - } - } - - spl::shared_ptr create_protocol(const std::wstring& name) const - { - if(boost::iequals(name, L"AMCP")) - return spl::make_shared(channels_); - else if(boost::iequals(name, L"CII")) - return spl::make_shared(channels_); - else if(boost::iequals(name, L"CLOCK")) - return spl::make_shared(channels_); - - BOOST_THROW_EXCEPTION(caspar_exception() << arg_name_info(L"name") << arg_value_info(name) << msg_info(L"Invalid protocol")); - } -}; - -server::server() : impl_(new impl()){} - -const std::vector> server::get_channels() const -{ - return impl_->channels_; -} -void server::subscribe(const monitor::observable::observer_ptr& o){impl_->event_subject_->subscribe(o);} -void server::unsubscribe(const monitor::observable::observer_ptr& o){impl_->event_subject_->unsubscribe(o);} - -} \ No newline at end of file +/* +* Copyright (c) 2011 Sveriges Television AB +* +* This file is part of CasparCG (www.casparcg.com). +* +* CasparCG 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 +* (at your option) any later version. +* +* CasparCG 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 CasparCG. If not, see . +* +* Author: Robert Nagy, ronag89@gmail.com +*/ + +#include "stdafx.h" + +#include "server.h" +#include "included_modules.h" +#include "default_audio_config.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace caspar { + +using namespace core; +using namespace protocol; + +std::shared_ptr create_running_io_service() +{ + auto service = std::make_shared(); + // To keep the io_service::run() running although no pending async + // operations are posted. + auto work = std::make_shared(*service); + auto weak_work = std::weak_ptr(work); + auto thread = std::make_shared([service, weak_work] + { + ensure_gpf_handler_installed_for_thread("asio-thread"); + + while (auto strong = weak_work.lock()) + { + try + { + service->run(); + } + catch (...) + { + CASPAR_LOG_CURRENT_EXCEPTION(); + } + } + + CASPAR_LOG(info) << "[asio] Global io_service uninitialized."; + }); + + return std::shared_ptr( + service.get(), + [service, work, thread](void*) mutable + { + CASPAR_LOG(info) << "[asio] Shutting down global io_service."; + work.reset(); + service->stop(); + if (thread->get_id() != boost::this_thread::get_id()) + thread->join(); + else + thread->detach(); + }); +} + +struct server::impl : boost::noncopyable +{ + std::shared_ptr io_service_ = create_running_io_service(); + spl::shared_ptr monitor_subject_; + spl::shared_ptr diag_subject_ = core::diagnostics::get_or_create_subject(); + accelerator::accelerator accelerator_; + spl::shared_ptr help_repo_; + std::shared_ptr amcp_command_repo_; + std::vector> async_servers_; + std::shared_ptr primary_amcp_server_; + std::shared_ptr osc_client_ = std::make_shared(io_service_); + std::vector> predefined_osc_subscriptions_; + std::vector> channels_; + spl::shared_ptr media_info_repo_; + boost::thread initial_media_info_thread_; + spl::shared_ptr system_info_provider_repo_; + spl::shared_ptr cg_registry_; + spl::shared_ptr producer_registry_; + spl::shared_ptr consumer_registry_; + tbb::atomic running_; + std::shared_ptr thumbnail_generator_; + std::promise& shutdown_server_now_; + + explicit impl(std::promise& shutdown_server_now) + : accelerator_(env::properties().get(L"configuration.accelerator", L"auto")) + , media_info_repo_(create_in_memory_media_info_repository()) + , producer_registry_(spl::make_shared(help_repo_)) + , consumer_registry_(spl::make_shared(help_repo_)) + , shutdown_server_now_(shutdown_server_now) + { + running_ = false; + core::diagnostics::register_graph_to_log_sink(); + caspar::core::diagnostics::osd::register_sink(); + diag_subject_->attach_parent(monitor_subject_); + + module_dependencies dependencies( + system_info_provider_repo_, + cg_registry_, + media_info_repo_, + producer_registry_, + consumer_registry_); + + initialize_modules(dependencies); + core::text::init(dependencies); + core::init_cg_proxy_as_producer(dependencies); + core::scene::init(dependencies); + core::syncto::init(dependencies); + help_repo_->register_item({ L"producer" }, L"Color Producer", &core::describe_color_producer); + } + + void start() + { + running_ = true; + + setup_audio_config(env::properties()); + CASPAR_LOG(info) << L"Initialized audio config."; + + setup_channels(env::properties()); + CASPAR_LOG(info) << L"Initialized channels."; + + setup_thumbnail_generation(env::properties()); + CASPAR_LOG(info) << L"Initialized thumbnail generator."; + + setup_controllers(env::properties()); + CASPAR_LOG(info) << L"Initialized controllers."; + + setup_osc(env::properties()); + CASPAR_LOG(info) << L"Initialized osc."; + + start_initial_media_info_scan(); + CASPAR_LOG(info) << L"Started initial media information retrieval."; + } + + ~impl() + { + if (running_) + { + running_ = false; + initial_media_info_thread_.join(); + } + + std::weak_ptr weak_io_service = io_service_; + io_service_.reset(); + osc_client_.reset(); + thumbnail_generator_.reset(); + amcp_command_repo_.reset(); + primary_amcp_server_.reset(); + async_servers_.clear(); + destroy_producers_synchronously(); + destroy_consumers_synchronously(); + channels_.clear(); + + while (weak_io_service.lock()) + boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); + + uninitialize_modules(); + core::diagnostics::osd::shutdown(); + } + + void setup_audio_config(const boost::property_tree::wptree& pt) + { + using boost::property_tree::wptree; + + auto default_config = get_default_audio_config(); + + // Start with the defaults + audio_channel_layout_repository::get_default()->register_all_layouts(default_config.get_child(L"audio.channel-layouts")); + audio_mix_config_repository::get_default()->register_all_configs(default_config.get_child(L"audio.mix-configs")); + + // Merge with user configuration (adds to or overwrites the defaults) + auto custom_channel_layouts = pt.get_child_optional(L"configuration.audio.channel-layouts"); + auto custom_mix_configs = pt.get_child_optional(L"configuration.audio.mix-configs"); + + if (custom_channel_layouts) + { + CASPAR_SCOPED_CONTEXT_MSG("/configuration/audio/channel-layouts"); + audio_channel_layout_repository::get_default()->register_all_layouts(*custom_channel_layouts); + } + + if (custom_mix_configs) + { + CASPAR_SCOPED_CONTEXT_MSG("/configuration/audio/mix-configs"); + audio_mix_config_repository::get_default()->register_all_configs(*custom_mix_configs); + } + } + + void setup_channels(const boost::property_tree::wptree& pt) + { + using boost::property_tree::wptree; + + std::vector xml_channels; + + for (auto& xml_channel : pt | witerate_children(L"configuration.channels") | welement_context_iteration) + { + xml_channels.push_back(xml_channel.second); + ptree_verify_element_name(xml_channel, L"channel"); + + auto format_desc_str = xml_channel.second.get(L"video-mode", L"PAL"); + auto format_desc = video_format_desc(format_desc_str); + if(format_desc.format == video_format::invalid) + CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid video-mode: " + format_desc_str)); + + auto channel_layout_str = xml_channel.second.get(L"channel-layout", L"stereo"); + auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(channel_layout_str); + if (!channel_layout) + CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Unknown channel-layout: " + channel_layout_str)); + + auto channel_id = static_cast(channels_.size() + 1); + auto channel = spl::make_shared(channel_id, format_desc, *channel_layout, accelerator_.create_image_mixer(channel_id)); + + channel->monitor_output().attach_parent(monitor_subject_); + channel->mixer().set_straight_alpha_output(xml_channel.second.get(L"straight-alpha-output", false)); + channels_.push_back(channel); + } + + for (auto& channel : channels_) + { + core::diagnostics::scoped_call_context save; + core::diagnostics::call_context::for_thread().video_channel = channel->index(); + + for (auto& xml_consumer : xml_channels.at(channel->index() - 1) | witerate_children(L"consumers") | welement_context_iteration) + { + auto name = xml_consumer.first; + + try + { + if (name != L"") + channel->output().add(consumer_registry_->create_consumer(name, xml_consumer.second, &channel->stage(), channels_)); + } + catch (const user_error& e) + { + CASPAR_LOG_CURRENT_EXCEPTION_AT_LEVEL(debug); + CASPAR_LOG(error) << get_message_and_context(e) << " Turn on log level debug for stacktrace."; + } + catch (...) + { + CASPAR_LOG_CURRENT_EXCEPTION(); + } + } + } + + // Dummy diagnostics channel + if (env::properties().get(L"configuration.channel-grid", false)) + { + auto channel_id = static_cast(channels_.size() + 1); + channels_.push_back(spl::make_shared( + channel_id, + core::video_format_desc(core::video_format::x576p2500), + *core::audio_channel_layout_repository::get_default()->get_layout(L"stereo"), + accelerator_.create_image_mixer(channel_id))); + channels_.back()->monitor_output().attach_parent(monitor_subject_); + } + } + + void setup_osc(const boost::property_tree::wptree& pt) + { + using boost::property_tree::wptree; + using namespace boost::asio::ip; + + monitor_subject_->attach_parent(osc_client_->sink()); + + auto default_port = + pt.get(L"configuration.osc.default-port", 6250); + auto disable_send_to_amcp_clients = + pt.get(L"configuration.osc.disable-send-to-amcp-clients", false); + auto predefined_clients = + pt.get_child_optional(L"configuration.osc.predefined-clients"); + + if (predefined_clients) + { + for (auto& predefined_client : pt | witerate_children(L"configuration.osc.predefined-clients") | welement_context_iteration) + { + ptree_verify_element_name(predefined_client, L"predefined-client"); + + const auto address = + ptree_get(predefined_client.second, L"address"); + const auto port = + ptree_get(predefined_client.second, L"port"); + predefined_osc_subscriptions_.push_back( + osc_client_->get_subscription_token(udp::endpoint( + address_v4::from_string(u8(address)), + port))); + } + } + + if (!disable_send_to_amcp_clients && primary_amcp_server_) + primary_amcp_server_->add_client_lifecycle_object_factory( + [=] (const std::string& ipv4_address) + -> std::pair> + { + using namespace boost::asio::ip; + + return std::make_pair( + std::wstring(L"osc_subscribe"), + osc_client_->get_subscription_token( + udp::endpoint( + address_v4::from_string( + ipv4_address), + default_port))); + }); + } + + void setup_thumbnail_generation(const boost::property_tree::wptree& pt) + { + if (!pt.get(L"configuration.thumbnails.generate-thumbnails", true)) + return; + + auto scan_interval_millis = pt.get(L"configuration.thumbnails.scan-interval-millis", 5000); + + polling_filesystem_monitor_factory monitor_factory(io_service_, scan_interval_millis); + thumbnail_generator_.reset(new thumbnail_generator( + monitor_factory, + env::media_folder(), + env::thumbnail_folder(), + pt.get(L"configuration.thumbnails.width", 256), + pt.get(L"configuration.thumbnails.height", 144), + core::video_format_desc(pt.get(L"configuration.thumbnails.video-mode", L"720p2500")), + accelerator_.create_image_mixer(0), + pt.get(L"configuration.thumbnails.generate-delay-millis", 2000), + &image::write_cropped_png, + media_info_repo_, + producer_registry_, + cg_registry_, + pt.get(L"configuration.thumbnails.mipmap", true))); + } + + void setup_controllers(const boost::property_tree::wptree& pt) + { + amcp_command_repo_ = spl::make_shared( + channels_, + thumbnail_generator_, + media_info_repo_, + system_info_provider_repo_, + cg_registry_, + help_repo_, + producer_registry_, + consumer_registry_, + accelerator_.get_ogl_device(), + shutdown_server_now_); + amcp::register_commands(*amcp_command_repo_); + + using boost::property_tree::wptree; + for (auto& xml_controller : pt | witerate_children(L"configuration.controllers") | welement_context_iteration) + { + auto name = xml_controller.first; + auto protocol = ptree_get(xml_controller.second, L"protocol"); + + if(name == L"tcp") + { + auto port = ptree_get(xml_controller.second, L"port"); + auto asyncbootstrapper = spl::make_shared( + io_service_, + create_protocol(protocol, L"TCP Port " + boost::lexical_cast(port)), + port); + async_servers_.push_back(asyncbootstrapper); + + if (!primary_amcp_server_ && boost::iequals(protocol, L"AMCP")) + primary_amcp_server_ = asyncbootstrapper; + } + else + CASPAR_LOG(warning) << "Invalid controller: " << name; + } + } + + IO::protocol_strategy_factory::ptr create_protocol(const std::wstring& name, const std::wstring& port_description) const + { + using namespace IO; + + if(boost::iequals(name, L"AMCP")) + return wrap_legacy_protocol("\r\n", spl::make_shared(port_description, spl::make_shared_ptr(amcp_command_repo_))); + else if(boost::iequals(name, L"CII")) + return wrap_legacy_protocol("\r\n", spl::make_shared(channels_, cg_registry_, producer_registry_)); + else if(boost::iequals(name, L"CLOCK")) + return spl::make_shared( + "ISO-8859-1", + spl::make_shared(channels_, cg_registry_, producer_registry_)); + else if (boost::iequals(name, L"LOG")) + return spl::make_shared(); + + CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid protocol: " + name)); + } + + void start_initial_media_info_scan() + { + initial_media_info_thread_ = boost::thread([this] + { + try + { + ensure_gpf_handler_installed_for_thread("initial media scan"); + + for (boost::filesystem::wrecursive_directory_iterator iter(env::media_folder()), end; iter != end; ++iter) + { + if (running_) + { + if (boost::filesystem::is_regular_file(iter->path())) + media_info_repo_->get(iter->path().wstring()); + } + else + { + CASPAR_LOG(info) << L"Initial media information retrieval aborted."; + return; + } + } + + CASPAR_LOG(info) << L"Initial media information retrieval finished."; + } + catch (...) + { + CASPAR_LOG_CURRENT_EXCEPTION(); + } + }); + } +}; + +server::server(std::promise& shutdown_server_now) : impl_(new impl(shutdown_server_now)){} +void server::start() { impl_->start(); } +spl::shared_ptr server::get_system_info_provider_repo() const { return impl_->system_info_provider_repo_; } +spl::shared_ptr server::get_amcp_command_repository() const { return spl::make_shared_ptr(impl_->amcp_command_repo_); } +core::monitor::subject& server::monitor_output() { return *impl_->monitor_subject_; } + +}