X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=shell%2Fmain.cpp;h=d742cb0d9c633f6fe2361cae894035aaa11178f3;hb=c96f667f533a88c197969959f6fb546da5105fbe;hp=10f6b5be80618f3d8ad0f9240aa3fc7369a90460;hpb=44e57e5b9c79ad612161339cab189591ccc98c79;p=casparcg diff --git a/shell/main.cpp b/shell/main.cpp index 10f6b5be8..d742cb0d9 100644 --- a/shell/main.cpp +++ b/shell/main.cpp @@ -25,65 +25,54 @@ #include "stdafx.h" -#ifdef _DEBUG +#include +#include + +#if defined _DEBUG && defined _MSC_VER #define _CRTDBG_MAP_ALLOC #include #include #else - #include + // Reenable when tbb gets official support for vc14 + //#include #endif -#include "resource.h" - #include "server.h" +#include "platform_specific.h" +#include "included_modules.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 -using namespace caspar; - -// NOTE: This is needed in order to make CComObject work since this is not a real ATL project. -CComModule _AtlModule; -extern __declspec(selectany) CAtlModule* _pAtlModule = &_AtlModule; +#include +#include -void change_icon( const HICON hNewIcon ) -{ - auto hMod = ::LoadLibrary(L"Kernel32.dll"); - typedef DWORD(__stdcall *SCI)(HICON); - auto pfnSetConsoleIcon = reinterpret_cast(::GetProcAddress(hMod, "SetConsoleIcon")); - pfnSetConsoleIcon(hNewIcon); - ::FreeLibrary(hMod); -} +#include +using namespace caspar; + void setup_global_locale() { boost::locale::generator gen; @@ -92,43 +81,6 @@ void setup_global_locale() std::locale::global(gen("")); } -void setup_console_window() -{ - auto hOut = GetStdHandle(STD_OUTPUT_HANDLE); - - // Disable close button in console to avoid shutdown without cleanup. - EnableMenuItem(GetSystemMenu(GetConsoleWindow(), FALSE), SC_CLOSE , MF_GRAYED); - DrawMenuBar(GetConsoleWindow()); - //SetConsoleCtrlHandler(HandlerRoutine, true); - - // Configure console size and position. - auto coord = GetLargestConsoleWindowSize(hOut); - coord.X /= 2; - - SetConsoleScreenBufferSize(hOut, coord); - - SMALL_RECT DisplayArea = {0, 0, 0, 0}; - DisplayArea.Right = coord.X-1; - DisplayArea.Bottom = (coord.Y-1)/2; - SetConsoleWindowInfo(hOut, TRUE, &DisplayArea); - - change_icon(::LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(101))); - - // Set console title. - std::wstringstream str; - str << "CasparCG Server " << env::version() << L" x64 "; -#ifdef COMPILE_RELEASE - str << " Release"; -#elif COMPILE_PROFILE - str << " Profile"; -#elif COMPILE_DEVELOP - str << " Develop"; -#elif COMPILE_DEBUG - str << " Debug"; -#endif - SetConsoleTitle(str.str().c_str()); -} - void print_info() { CASPAR_LOG(info) << L"############################################################################"; @@ -138,147 +90,168 @@ void print_info() CASPAR_LOG(info) << L"http://www.casparcg.com/"; CASPAR_LOG(info) << L"############################################################################"; CASPAR_LOG(info) << L"Starting CasparCG Video and Graphics Playout Server " << env::version(); - CASPAR_LOG(info) << L"on " << win_product_name() << L" " << win_sp_version(); + CASPAR_LOG(info) << L"on " << os_description(); CASPAR_LOG(info) << cpu_info(); CASPAR_LOG(info) << system_product_name(); - - CASPAR_LOG(info) << L"Decklink " << decklink::version(); - BOOST_FOREACH(auto device, decklink::device_list()) - CASPAR_LOG(info) << L" - " << device; - - CASPAR_LOG(info) << L"Bluefish " << bluefish::version(); - BOOST_FOREACH(auto device, bluefish::device_list()) - CASPAR_LOG(info) << L" - " << device; - - CASPAR_LOG(info) << L"Flash " << flash::version(); - CASPAR_LOG(info) << L"FreeImage " << image::version(); - CASPAR_LOG(info) << L"FFMPEG-avcodec " << ffmpeg::avcodec_version(); - CASPAR_LOG(info) << L"FFMPEG-avformat " << ffmpeg::avformat_version(); - CASPAR_LOG(info) << L"FFMPEG-avfilter " << ffmpeg::avfilter_version(); - CASPAR_LOG(info) << L"FFMPEG-avutil " << ffmpeg::avutil_version(); - CASPAR_LOG(info) << L"FFMPEG-swscale " << ffmpeg::swscale_version(); } -LONG WINAPI UserUnhandledExceptionFilter(EXCEPTION_POINTERS* info) + +void print_system_info(const spl::shared_ptr& repo) { - try - { - CASPAR_LOG(fatal) << L"#######################\n UNHANDLED EXCEPTION: \n" - << L"Adress:" << info->ExceptionRecord->ExceptionAddress << L"\n" - << L"Code:" << info->ExceptionRecord->ExceptionCode << L"\n" - << L"Flag:" << info->ExceptionRecord->ExceptionFlags << L"\n" - << L"Info:" << info->ExceptionRecord->ExceptionInformation << L"\n" - << L"Continuing execution. \n#######################"; - - CASPAR_LOG_CALL_STACK(); - } - catch(...){} + boost::property_tree::wptree info; + repo->fill_information(info); - return EXCEPTION_CONTINUE_EXECUTION; + for (auto& elem : info.get_child(L"system")) + log::print_child(boost::log::trivial::info, L"", elem.first, elem.second); } -void run() +void do_run( + std::weak_ptr> amcp, + std::promise& shutdown_server_now, + tbb::atomic& should_wait_for_keypress) { - // Create server object which initializes channels, protocols and controllers. - server caspar_server; - - auto server = spl::make_shared(5253); - caspar_server.subscribe(server); - - //auto console_obs = reactive::make_observer([](const monitor::event& e) - //{ - // std::stringstream str; - // str << e; - // CASPAR_LOG(trace) << str.str().c_str(); - //}); - - //caspar_server.subscribe(console_obs); - - // Create a amcp parser for console commands. - protocol::amcp::AMCPProtocolStrategy amcp(caspar_server.channels()); - - // Create a dummy client which prints amcp responses to console. - auto console_client = std::make_shared(); - std::wstring wcmd; while(true) { std::getline(std::wcin, wcmd); // TODO: It's blocking... - boost::to_upper(wcmd); + //boost::to_upper(wcmd); - if(wcmd == L"EXIT" || wcmd == L"Q" || wcmd == L"QUIT" || wcmd == L"BYE") - break; - - // This is just dummy code for testing. - if(wcmd.substr(0, 1) == L"1") - wcmd = L"LOADBG 1-1 " + wcmd.substr(1, wcmd.length()-1) + L" SLIDE 100 LOOP \r\nPLAY 1-1"; - else if(wcmd.substr(0, 1) == L"2") - wcmd = L"MIXER 1-0 VIDEO IS_KEY 1"; - else if(wcmd.substr(0, 1) == L"3") - wcmd = L"CG 1-2 ADD 1 BBTELEFONARE 1"; - else if(wcmd.substr(0, 1) == L"4") - wcmd = L"PLAY 1-1 DV FILTER yadif=1:-1 LOOP"; - else if(wcmd.substr(0, 1) == L"5") - { - auto file = wcmd.substr(2, wcmd.length()-1); - wcmd = L"PLAY 1-1 " + file + L" LOOP\r\n" - L"PLAY 1-2 " + file + L" LOOP\r\n" - L"PLAY 1-3 " + file + L" LOOP\r\n" - L"PLAY 2-1 " + file + L" LOOP\r\n" - L"PLAY 2-2 " + file + L" LOOP\r\n" - L"PLAY 2-3 " + file + L" LOOP\r\n"; - } - else if(wcmd.substr(0, 1) == L"7") - { - wcmd = L""; - wcmd += L"CLEAR 1\r\n"; - wcmd += L"MIXER 1 CLEAR\r\n"; - wcmd += L"PLAY 1-0 GREEN\r\n"; - wcmd += L"PLAY 1-1 BLUE\r\n"; - wcmd += L"CG 1-2 ADD 1 ECS_TEST 1\r\n"; - wcmd += L"MIXER 1-2 FILL 0 -1 1 2\r\n"; - } - else if(wcmd.substr(0, 1) == L"8") + if(boost::iequals(wcmd, L"EXIT") || boost::iequals(wcmd, L"Q") || boost::iequals(wcmd, L"QUIT") || boost::iequals(wcmd, L"BYE")) { - wcmd = L""; - wcmd += L"MIXER 1-1 FILL 0.0 0.5 1.0 1.0 500 linear DEFER\r\n"; - wcmd += L"MIXER 1-2 FILL 0.0 0.0 1.0 1.0 500 linear DEFER\r\n"; - wcmd += L"MIXER 1 COMMIT\r\n"; + CASPAR_LOG(info) << L"Received message from Console: " << wcmd << L"\\r\\n"; + should_wait_for_keypress = true; + shutdown_server_now.set_value(false); //false to not restart + break; } - else if(wcmd.substr(0, 1) == L"X") + + try { - int num = 0; - std::wstring file; - try + // This is just dummy code for testing. + if(wcmd.substr(0, 1) == L"1") + wcmd = L"LOADBG 1-1 " + wcmd.substr(1, wcmd.length()-1) + L" SLIDE 100 LOOP \r\nPLAY 1-1"; + else if(wcmd.substr(0, 1) == L"2") + wcmd = L"MIXER 1-0 VIDEO IS_KEY 1"; + else if(wcmd.substr(0, 1) == L"3") + wcmd = L"CG 1-2 ADD 1 BBTELEFONARE 1"; + else if(wcmd.substr(0, 1) == L"4") + wcmd = L"PLAY 1-1 DV FILTER yadif=1:-1 LOOP"; + else if(wcmd.substr(0, 1) == L"5") { - num = boost::lexical_cast(wcmd.substr(1, 2)); - file = wcmd.substr(4, wcmd.length()-1); + auto file = wcmd.substr(2, wcmd.length()-1); + wcmd = L"PLAY 1-1 " + file + L" LOOP\r\n" + L"PLAY 1-2 " + file + L" LOOP\r\n" + L"PLAY 1-3 " + file + L" LOOP\r\n" + L"PLAY 2-1 " + file + L" LOOP\r\n" + L"PLAY 2-2 " + file + L" LOOP\r\n" + L"PLAY 2-3 " + file + L" LOOP\r\n"; } - catch(...) + else if(wcmd.substr(0, 1) == L"7") { - num = boost::lexical_cast(wcmd.substr(1, 1)); - file = wcmd.substr(3, wcmd.length()-1); + wcmd = L""; + wcmd += L"CLEAR 1\r\n"; + wcmd += L"MIXER 1 CLEAR\r\n"; + wcmd += L"PLAY 1-0 GREEN\r\n"; + wcmd += L"PLAY 1-1 BLUE\r\n"; + wcmd += L"CG 1-2 ADD 1 ECS_TEST 1\r\n"; + wcmd += L"MIXER 1-2 FILL 0 -1 1 2\r\n"; } - - int n = 0; - int num2 = num; - while(num2 > 0) + else if(wcmd.substr(0, 1) == L"8") { - num2 >>= 1; - n++; + wcmd = L""; + wcmd += L"MIXER 1-1 FILL 0.0 0.5 1.0 1.0 500 linear DEFER\r\n"; + wcmd += L"MIXER 1-2 FILL 0.0 0.0 1.0 1.0 500 linear DEFER\r\n"; + wcmd += L"MIXER 1 COMMIT\r\n"; + } + else if(wcmd.substr(0, 1) == L"X") + { + int num = 0; + std::wstring file; + try + { + num = boost::lexical_cast(wcmd.substr(1, 2)); + file = wcmd.substr(4, wcmd.length()-1); + } + catch(...) + { + num = boost::lexical_cast(wcmd.substr(1, 1)); + file = wcmd.substr(3, wcmd.length()-1); + } + + int n = 0; + int num2 = num; + while(num2 > 0) + { + num2 >>= 1; + n++; + } + + wcmd = L"MIXER 1 GRID " + boost::lexical_cast(n); + + for(int i = 1; i <= num; ++i) + wcmd += L"\r\nPLAY 1-" + boost::lexical_cast(i) + L" " + file + L" LOOP";// + L" SLIDE 100 LOOP"; } - - wcmd = L"MIXER 1 GRID " + boost::lexical_cast(n); - - for(int i = 1; i <= num; ++i) - wcmd += L"\r\nPLAY 1-" + boost::lexical_cast(i) + L" " + file + L" LOOP";// + L" SLIDE 100 LOOP"; + } + catch (...) + { + CASPAR_LOG_CURRENT_EXCEPTION(); + continue; } wcmd += L"\r\n"; - amcp.Parse(wcmd.c_str(), static_cast(wcmd.length()), console_client); - } - CASPAR_LOG(info) << "Successfully shutdown CasparCG Server."; + auto strong = amcp.lock(); + if (strong) + strong->parse(wcmd); + else + break; + } +}; + +bool run(const std::wstring& config_file_name, tbb::atomic& should_wait_for_keypress) +{ + std::promise shutdown_server_now; + std::future shutdown_server = shutdown_server_now.get_future(); + + print_info(); + + // Create server object which initializes channels, protocols and controllers. + std::unique_ptr caspar_server(new server(shutdown_server_now)); + + // Print environment information. + print_system_info(caspar_server->get_system_info_provider_repo()); + + std::wstringstream str; + boost::property_tree::xml_writer_settings w(' ', 3); + boost::property_tree::write_xml(str, env::properties(), w); + CASPAR_LOG(info) << config_file_name << L":\n-----------------------------------------\n" << str.str() << L"-----------------------------------------"; + + { + CASPAR_SCOPED_CONTEXT_MSG(config_file_name + L": ") + caspar_server->start(); + } + + // Create a dummy client which prints amcp responses to console. + auto console_client = spl::make_shared(); + + // Create a amcp parser for console commands. + std::shared_ptr> amcp = spl::make_shared>( + L"\r\n", + spl::make_shared( + spl::make_shared( + L"Console", + caspar_server->get_amcp_command_repository())))->create(console_client); + std::weak_ptr> weak_amcp = amcp; + + // Use separate thread for the blocking console input, will be terminated + // anyway when the main thread terminates. + boost::thread stdin_thread(std::bind(do_run, weak_amcp, std::ref(shutdown_server_now), std::ref(should_wait_for_keypress))); //compiler didn't like lambda here... + stdin_thread.detach(); + bool should_restart = shutdown_server.get(); + amcp.reset(); + + while (weak_amcp.lock()); + + return should_restart; } void on_abort(int) @@ -286,101 +259,103 @@ void on_abort(int) CASPAR_THROW_EXCEPTION(invalid_operation() << msg_info("abort called")); } -int main(int argc, wchar_t* argv[]) -{ - SetUnhandledExceptionFilter(UserUnhandledExceptionFilter); - signal(SIGABRT, on_abort); +int main(int argc, char** argv) +{ + if (intercept_command_line_args(argc, argv)) + return 0; + + int return_code = 0; + setup_prerequisites(); + //std::signal(SIGABRT, on_abort); setup_global_locale(); std::wcout << L"Type \"q\" to close application." << std::endl; // Set debug mode. - #ifdef _DEBUG - HANDLE hLogFile; - hLogFile = CreateFile(L"crt_log.txt", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - std::shared_ptr crt_log(nullptr, [](HANDLE h){::CloseHandle(h);}); - - _CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_WARN, hLogFile); - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ERROR, hLogFile); - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_ASSERT, hLogFile); - #endif + auto debugging_environment = setup_debugging_environment(); // Increase process priotity. - SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); + increase_process_priority(); - // Install structured exception handler. - win32_exception::install_handler(); - - // Increase time precision. This will increase accuracy of function like Sleep(1) from 10 ms to 1 ms. - struct inc_prec - { - inc_prec(){timeBeginPeriod(1);} - ~inc_prec(){timeEndPeriod(1);} - } inc_prec; + // Install general protection fault handler. + ensure_gpf_handler_installed_for_thread("main thread"); - // Install SEH into all tbb threads. + // Install GPF handler into all tbb threads. struct tbb_thread_installer : public tbb::task_scheduler_observer { tbb_thread_installer(){observe(true);} - void on_scheduler_entry(bool is_worker) + void on_scheduler_entry(bool is_worker) override { - //detail::SetThreadName(GetCurrentThreadId(), "tbb-worker-thread"); - win32_exception::install_handler(); + ensure_gpf_handler_installed_for_thread("tbb-worker-thread"); } } tbb_thread_installer; tbb::task_scheduler_init init; + std::wstring config_file_name(L"casparcg.config"); try { // Configure environment properties from configuration. - env::configure(L"casparcg.config"); - - log::set_log_level(env::properties().get(L"configuration.log-level", L"debug")); + if (argc >= 2) + config_file_name = caspar::u16(argv[1]); + + env::configure(config_file_name); + + log::set_log_level(env::properties().get(L"configuration.log-level", L"info")); + auto log_categories_str = env::properties().get(L"configuration.log-categories", L"communication"); + std::set log_categories; + boost::split(log_categories, log_categories_str, boost::is_any_of(L", ")); + for (auto& log_category : { L"calltrace", L"communication" }) + log::set_log_category(log_category, log_categories.find(log_category) != log_categories.end()); - #ifdef _DEBUG - if(env::properties().get(L"configuration.debugging.remote", false)) - MessageBox(nullptr, TEXT("Now is the time to connect for remote debugging..."), TEXT("Debug"), MB_OK | MB_TOPMOST); - #endif + if (env::properties().get(L"configuration.debugging.remote", false)) + wait_for_remote_debugging(); // Start logging to file. - log::add_file_sink(env::log_folder()); + log::add_file_sink(env::log_folder() + L"caspar", caspar::log::category != caspar::log::log_category::calltrace); + log::add_file_sink(env::log_folder() + L"calltrace", caspar::log::category == caspar::log::log_category::calltrace); std::wcout << L"Logging [info] or higher severity to " << env::log_folder() << std::endl << std::endl; // Setup console window. setup_console_window(); - // Print environment information. - print_info(); - - std::wstringstream str; - boost::property_tree::xml_writer_settings w(' ', 3); - boost::property_tree::write_xml(str, env::properties(), w); - CASPAR_LOG(info) << L"casparcg.config:\n-----------------------------------------\n" << str.str().c_str() << L"-----------------------------------------"; - - run(); + tbb::atomic should_wait_for_keypress; + should_wait_for_keypress = false; + auto should_restart = run(config_file_name, should_wait_for_keypress); + return_code = should_restart ? 5 : 0; + + for (auto& thread : get_thread_infos()) + { + if (thread->name != "main thread" && thread->name != "tbb-worker-thread") + CASPAR_LOG(warning) << L"Thread left running: " << thread->name << L" (" << thread->native_id << L")"; + } - system("pause"); + CASPAR_LOG(info) << "Successfully shutdown CasparCG Server."; + + if (should_wait_for_keypress) + wait_for_keypress(); } - catch(boost::property_tree::file_parser_error&) + catch(const boost::property_tree::file_parser_error& e) { CASPAR_LOG_CURRENT_EXCEPTION(); - CASPAR_LOG(fatal) << L"Unhandled configuration error in main thread. Please check the configuration file (casparcg.config) for errors."; - system("pause"); + CASPAR_LOG(fatal) << "At " << u8(config_file_name) << ":" << e.line() << ": " << e.message() << ". Please check the configuration file (" << u8(config_file_name) << ") for errors."; + wait_for_keypress(); + } + catch (const user_error& e) + { + CASPAR_LOG_CURRENT_EXCEPTION_AT_LEVEL(debug); + CASPAR_LOG(fatal) << get_message_and_context(e) << " Please check the configuration file (" << u8(config_file_name) << ") for errors. Turn on log level debug for stacktrace."; + wait_for_keypress(); } catch(...) { CASPAR_LOG_CURRENT_EXCEPTION(); CASPAR_LOG(fatal) << L"Unhandled exception in main thread. Please report this error on the CasparCG forums (www.casparcg.com/forum)."; - Sleep(1000); + boost::this_thread::sleep_for(boost::chrono::milliseconds(1000)); std::wcout << L"\n\nCasparCG will automatically shutdown. See the log file located at the configured log-file folder for more information.\n\n"; - Sleep(4000); - } - - return 0; -} \ No newline at end of file + boost::this_thread::sleep_for(boost::chrono::milliseconds(4000)); + } + + return return_code; +}