#include "flash_producer.h"
#include "FlashAxContainer.h"
+#include "../util/swf.h"
+
#include <core/video_format.h>
#include <core/frame/frame.h>
#include <core/frame/draw_frame.h>
#include <core/frame/frame_factory.h>
#include <core/frame/pixel_format.h>
+#include <core/producer/frame_producer.h>
#include <core/monitor/monitor.h>
+#include <core/help/help_repository.h>
+#include <core/help/help_sink.h>
#include <common/env.h>
#include <common/executor.h>
try
{
std::vector<template_host> template_hosts;
- BOOST_FOREACH(auto& xml_mapping, env::properties().get_child(L"configuration.template-hosts"))
+ auto template_hosts_element = env::properties().get_child_optional(
+ L"configuration.template-hosts");
+
+ if (template_hosts_element)
{
- try
+ for (auto& xml_mapping : *template_hosts_element)
{
- template_host template_host;
- template_host.video_mode = xml_mapping.second.get(L"video-mode", L"");
- template_host.filename = xml_mapping.second.get(L"filename", L"cg.fth");
- template_host.width = xml_mapping.second.get(L"width", desc.width);
- template_host.height = xml_mapping.second.get(L"height", desc.height);
- template_hosts.push_back(template_host);
+ try
+ {
+ template_host template_host;
+ template_host.video_mode = xml_mapping.second.get(L"video-mode", L"");
+ template_host.filename = xml_mapping.second.get(L"filename", L"cg.fth");
+ template_host.width = xml_mapping.second.get(L"width", desc.width);
+ template_host.height = xml_mapping.second.get(L"height", desc.height);
+ template_hosts.push_back(template_host);
+ }
+ catch(...){}
}
- catch(...){}
}
auto template_host_it = boost::find_if(template_hosts, [&](template_host template_host){return template_host.video_mode == desc.name;});
return template_host;
}
+boost::mutex& get_global_init_destruct_mutex()
+{
+ static boost::mutex m;
+
+ return m;
+}
+
class flash_renderer
{
struct com_init
{
- HRESULT result_;
+ HRESULT result_ = CoInitialize(nullptr);
com_init()
- : result_(CoInitialize(nullptr))
{
if(FAILED(result_))
CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info("Failed to initialize com-context for flash-player"));
}
} com_init_;
- monitor::basic_subject& event_subject_;
+ core::monitor::subject& monitor_subject_;
- const std::wstring filename_;
+ const std::wstring filename_;
+ const int width_;
+ const int height_;
- const std::shared_ptr<core::frame_factory> frame_factory_;
-
- CComObject<caspar::flash::FlashAxContainer>* ax_;
- core::draw_frame head_;
- bitmap bmp_;
+ const std::shared_ptr<core::frame_factory> frame_factory_;
- spl::shared_ptr<diagnostics::graph> graph_;
+ CComObject<caspar::flash::FlashAxContainer>* ax_ = nullptr;
+ core::draw_frame head_ = core::draw_frame::late();
+ bitmap bmp_ { width_, height_ };
+ prec_timer timer_;
+ caspar::timer tick_timer_;
- prec_timer timer_;
-
- const int width_;
- const int height_;
+ spl::shared_ptr<diagnostics::graph> graph_;
public:
- flash_renderer(monitor::basic_subject& event_subject, const spl::shared_ptr<diagnostics::graph>& graph, const std::shared_ptr<core::frame_factory>& frame_factory, const std::wstring& filename, int width, int height)
- : event_subject_(event_subject)
+ flash_renderer(core::monitor::subject& monitor_subject, const spl::shared_ptr<diagnostics::graph>& graph, const std::shared_ptr<core::frame_factory>& frame_factory, const std::wstring& filename, int width, int height)
+ : monitor_subject_(monitor_subject)
, graph_(graph)
, filename_(filename)
- , frame_factory_(frame_factory)
- , ax_(nullptr)
- , head_(core::draw_frame::empty())
- , bmp_(width, height)
, width_(width)
, height_(height)
+ , frame_factory_(frame_factory)
+ , bmp_(width, height)
{
graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));
- graph_->set_color("param", diagnostics::color(1.0f, 0.5f, 0.0f));
- graph_->set_color("sync", diagnostics::color(0.8f, 0.3f, 0.2f));
+ graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
+ graph_->set_color("param", diagnostics::color(1.0f, 0.5f, 0.0f));
if(FAILED(CComObject<caspar::flash::FlashAxContainer>::CreateInstance(&ax_)))
CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to create FlashAxContainer"));
- ax_->set_print([this]{return print();});
-
if(FAILED(ax_->CreateAxControl()))
CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Create FlashAxControl"));
-
+
+ ax_->set_print([this]{return print(); });
+
CComPtr<IShockwaveFlash> spFlash;
if(FAILED(ax_->QueryControl(&spFlash)))
CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Query FlashAxControl"));
if(FAILED(spFlash->put_Playing(true)) )
CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to start playing Flash"));
- if(FAILED(spFlash->put_Movie(CComBSTR(filename.c_str()))))
- CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Load Template Host"));
+ // Concurrent initialization of two template hosts causes a
+ // SecurityException later when CG ADD is performed. Initialization is
+ // therefore serialized via a global mutex.
+ lock(get_global_init_destruct_mutex(), [&]
+ {
+ if (FAILED(spFlash->put_Movie(CComBSTR(filename.c_str()))))
+ CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Load Template Host"));
+ });
if(FAILED(spFlash->put_ScaleMode(2))) //Exact fit. Scale without respect to the aspect ratio.
CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Set Scale Mode"));
- ax_->SetSize(width_, height_);
-
- tick(false);
- render();
+ ax_->SetSize(width_, height_);
+ render_frame(0.0);
CASPAR_LOG(info) << print() << L" Initialized.";
}
if(!ax_->FlashCall(param, result))
CASPAR_LOG(warning) << print() << L" Flash call failed:" << param;//CASPAR_THROW_EXCEPTION(invalid_operation() << msg_info("Flash function call failed.") << arg_name_info("param") << arg_value_info(narrow(param)));
- graph_->set_tag("param");
-
- return result;
- }
- void tick(double sync)
- {
- const float frame_time = 1.0f/ax_->GetFPS();
+ if (boost::starts_with(result, L"<exception>"))
+ CASPAR_LOG(warning) << print() << L" Flash call failed:" << result;
- if(sync > 0.00001)
- timer_.tick(frame_time*sync); // This will block the thread.
- else
- graph_->set_tag("sync");
+ graph_->set_tag("param");
- graph_->set_value("sync", sync);
- event_subject_ << monitor::event("sync") % sync;
-
- ax_->Tick();
-
- MSG msg;
- while(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) // DO NOT REMOVE THE MESSAGE DISPATCH LOOP. Without this some stuff doesn't work!
- {
- if(msg.message == WM_TIMER && msg.wParam == 3 && msg.lParam == 0) // We tick this inside FlashAxContainer
- continue;
-
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
+ return result;
}
- core::draw_frame render()
+ core::draw_frame render_frame(double sync)
{
const float frame_time = 1.0f/fps();
+ if (!ax_->IsReadyToRender())
+ return head_;
+
+ if (is_empty())
+ return core::draw_frame::empty();
+
+ if (sync > 0.00001)
+ timer_.tick(frame_time*sync); // This will block the thread.
+
+ graph_->set_value("tick-time", static_cast<float>(tick_timer_.elapsed() / frame_time) * 0.5f);
+ tick_timer_.restart();
boost::timer frame_timer;
+ ax_->Tick();
- if(ax_->InvalidRect())
+ if (ax_->InvalidRect())
{
- A_memset(bmp_.data(), 0, width_*height_*4);
- ax_->DrawControl(bmp_);
-
core::pixel_format_desc desc = core::pixel_format::bgra;
desc.planes.push_back(core::pixel_format_desc::plane(width_, height_, 4));
auto frame = frame_factory_->create_frame(this, desc);
+ A_memset(bmp_.data(), 0, width_ * height_ * 4);
+ ax_->DrawControl(bmp_);
+
A_memcpy(frame.image_data(0).begin(), bmp_.data(), width_*height_*4);
head_ = core::draw_frame(std::move(frame));
}
-
+
+ MSG msg;
+ while (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) // DO NOT REMOVE THE MESSAGE DISPATCH LOOP. Without this some stuff doesn't work!
+ {
+ if (msg.message == WM_TIMER && msg.wParam == 3 && msg.lParam == 0) // We tick this inside FlashAxContainer
+ continue;
+
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
graph_->set_value("frame-time", static_cast<float>(frame_timer.elapsed()/frame_time)*0.5f);
- event_subject_ << monitor::event("renderer/profiler/time") % frame_timer.elapsed() % frame_time;
+ monitor_subject_ << core::monitor::message("/renderer/profiler/time") % frame_timer.elapsed() % frame_time;
return head_;
}
std::wstring print()
{
- return L"flash-player[" + boost::filesystem::wpath(filename_).filename().wstring()
+ return L"flash-player[" + boost::filesystem::path(filename_).filename().wstring()
+ L"|" + boost::lexical_cast<std::wstring>(width_)
+ L"x" + boost::lexical_cast<std::wstring>(height_)
+ L"]";
struct flash_producer : public core::frame_producer_base
{
- monitor::basic_subject event_subject_;
+ core::monitor::subject monitor_subject_;
const std::wstring filename_;
const spl::shared_ptr<core::frame_factory> frame_factory_;
const core::video_format_desc format_desc_;
const int width_;
const int height_;
- const int buffer_size_;
+ core::constraints constraints_ { static_cast<double>(width_), static_cast<double>(height_) };
+ const int buffer_size_ = env::properties().get(L"configuration.flash.buffer-depth", format_desc_.fps > 30.0 ? 4 : 2);
tbb::atomic<int> fps_;
std::queue<core::draw_frame> frame_buffer_;
tbb::concurrent_bounded_queue<core::draw_frame> output_buffer_;
- core::draw_frame last_frame_;
+ core::draw_frame last_frame_ = core::draw_frame::empty();
- boost::timer tick_timer_;
std::unique_ptr<flash_renderer> renderer_;
-
- executor executor_;
+ tbb::atomic<bool> has_renderer_;
+
+ executor executor_ = L"flash_producer";
public:
flash_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::wstring& filename, int width, int height)
: filename_(filename)
, format_desc_(format_desc)
, width_(width > 0 ? width : format_desc.width)
, height_(height > 0 ? height : format_desc.height)
- , buffer_size_(env::properties().get(L"configuration.flash.buffer-depth", format_desc.fps > 30.0 ? 4 : 2))
- , executor_(L"flash_producer")
{
fps_ = 0;
- graph_->set_color("buffer-size", diagnostics::color(1.0f, 1.0f, 0.0f));
- graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
+ graph_->set_color("buffered", diagnostics::color(1.0f, 1.0f, 0.0f));
graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.9f));
graph_->set_text(print());
diagnostics::register_graph(graph_);
+ has_renderer_ = false;
+
CASPAR_LOG(info) << print() << L" Initialized";
}
}
// frame_producer
-
+
+ void log_buffered()
+ {
+ double buffered = output_buffer_.size();
+ auto ratio = buffered / buffer_size_;
+ graph_->set_value("buffered", ratio);
+ }
+
core::draw_frame receive_impl() override
{
auto frame = last_frame_;
+
+ double buffered = output_buffer_.size();
+ auto ratio = buffered / buffer_size_;
+ graph_->set_value("buffered", ratio);
- if(output_buffer_.try_pop(frame))
- executor_.begin_invoke(std::bind(&flash_producer::next, this));
+ if (output_buffer_.try_pop(frame))
+ last_frame_ = frame;
else
- graph_->set_tag("late-frame");
+ graph_->set_tag("late-frame");
+
+ fill_buffer();
- event_subject_ << monitor::event("host/path") % filename_
- << monitor::event("host/width") % width_
- << monitor::event("host/height") % height_
- << monitor::event("host/fps") % fps_
- << monitor::event("buffer") % output_buffer_.size() % buffer_size_;
+ monitor_subject_ << core::monitor::message("/host/path") % filename_
+ << core::monitor::message("/host/width") % width_
+ << core::monitor::message("/host/height") % height_
+ << core::monitor::message("/host/fps") % fps_
+ << core::monitor::message("/buffer") % output_buffer_.size() % buffer_size_;
+
+ return frame;
+ }
- return last_frame_ = frame;
+ core::constraints& pixel_constraints() override
+ {
+ return constraints_;
}
- boost::unique_future<std::wstring> call(const std::wstring& param) override
- {
+ std::future<std::wstring> call(const std::vector<std::wstring>& params) override
+ {
+ auto param = boost::algorithm::join(params, L" ");
+
+ if (param == L"?")
+ return make_ready_future(std::wstring(has_renderer_ ? L"1" : L"0"));
+
return executor_.begin_invoke([this, param]() -> std::wstring
{
try
{
- if(!renderer_)
+ bool initialize_renderer = !renderer_;
+
+ if (initialize_renderer)
{
- renderer_.reset(new flash_renderer(event_subject_, graph_, frame_factory_, filename_, width_, height_));
+ renderer_.reset(new flash_renderer(monitor_subject_, graph_, frame_factory_, filename_, width_, height_));
- while(output_buffer_.size() < buffer_size_)
- output_buffer_.push(core::draw_frame::empty());
+ has_renderer_ = true;
}
- return renderer_->call(param);
+ std::wstring result = param == L"start_rendering"
+ ? L"" : renderer_->call(param);
+
+ if (initialize_renderer)
+ {
+ do_fill_buffer(true);
+ }
+
+ return result;
}
catch(...)
{
CASPAR_LOG_CURRENT_EXCEPTION();
renderer_.reset(nullptr);
+ has_renderer_ = false;
}
return L"";
- });
+ }, task_priority::high_priority);
}
std::wstring print() const override
return info;
}
- void subscribe(const monitor::observable::observer_ptr& o) override
+ core::monitor::subject& monitor_output()
{
- event_subject_.subscribe(o);
+ return monitor_subject_;
}
- void unsubscribe(const monitor::observable::observer_ptr& o) override
+ // flash_producer
+
+ void fill_buffer()
{
- event_subject_.unsubscribe(o);
+ if (executor_.size() > 0)
+ return;
+
+ executor_.begin_invoke([this]
+ {
+ do_fill_buffer(false);
+ });
}
- // flash_producer
-
- void tick()
+ void do_fill_buffer(bool initial_buffer_fill)
{
- double ratio = std::min(1.0, static_cast<double>(output_buffer_.size())/static_cast<double>(std::max(1, buffer_size_ - 1)));
- double sync = 2*ratio - ratio*ratio;
- renderer_->tick(sync);
+ int nothing_rendered = 0;
+ const int MAX_NOTHING_RENDERED_RETRIES = 4;
+
+ auto to_render = buffer_size_ - output_buffer_.size();
+ bool allow_faster_rendering = !initial_buffer_fill;
+ int rendered = 0;
+
+ while (rendered < to_render)
+ {
+ bool was_rendered = next(allow_faster_rendering);
+ log_buffered();
+
+ if (was_rendered)
+ {
+ ++rendered;
+ }
+ else
+ {
+ if (nothing_rendered++ < MAX_NOTHING_RENDERED_RETRIES)
+ {
+ // Flash player not ready with first frame, sleep to not busy-loop;
+ boost::this_thread::sleep(boost::posix_time::milliseconds(10));
+ boost::this_thread::yield();
+ }
+ else
+ return;
+ }
+
+ executor_.yield(task_priority::high_priority);
+ }
}
-
- void next()
- {
- if(!renderer_)
- frame_buffer_.push(core::draw_frame::empty());
- tick_timer_.restart();
+ bool next(bool allow_faster_rendering)
+ {
+ if (!renderer_)
+ frame_buffer_.push(core::draw_frame::empty());
- if(frame_buffer_.empty())
- {
- tick();
- auto frame = renderer_->render();
+ if (frame_buffer_.empty())
+ {
+ if (abs(renderer_->fps() / 2.0 - format_desc_.fps) < 2.0) // flash == 2 * format -> interlace
+ {
+ auto frame1 = render_frame(allow_faster_rendering);
- if(abs(renderer_->fps()/2.0 - format_desc_.fps) < 2.0) // flash == 2 * format -> interlace
- {
- tick();
- if(format_desc_.field_mode != core::field_mode::progressive)
- frame = core::draw_frame::interlace(frame, renderer_->render(), format_desc_.field_mode);
-
- frame_buffer_.push(frame);
+ if (frame1 != core::draw_frame::late())
+ {
+ auto frame2 = render_frame(allow_faster_rendering);
+ frame_buffer_.push(core::draw_frame::interlace(frame1, frame2, format_desc_.field_mode));
+ }
}
- else if(abs(renderer_->fps() - format_desc_.fps/2.0) < 2.0) // format == 2 * flash -> duplicate
+ else if (abs(renderer_->fps() - format_desc_.fps / 2.0) < 2.0) // format == 2 * flash -> duplicate
{
- frame_buffer_.push(frame);
- frame_buffer_.push(frame);
+ auto frame = render_frame(allow_faster_rendering);
+
+ if (frame != core::draw_frame::late())
+ {
+ frame_buffer_.push(frame);
+ frame_buffer_.push(frame);
+ }
}
else //if(abs(renderer_->fps() - format_desc_.fps) < 0.1) // format == flash -> simple
{
- frame_buffer_.push(frame);
+ auto frame = render_frame(allow_faster_rendering);
+
+ if (frame != core::draw_frame::late())
+ frame_buffer_.push(frame);
}
-
- fps_.fetch_and_store(static_cast<int>(renderer_->fps()*100.0));
+
+ fps_.fetch_and_store(static_cast<int>(renderer_->fps()*100.0));
graph_->set_text(print());
-
- if(renderer_->is_empty())
+
+ if (renderer_->is_empty())
+ {
renderer_.reset();
+ has_renderer_ = false;
+ }
}
- graph_->set_value("tick-time", static_cast<float>(tick_timer_.elapsed()/fps_)*0.5f);
- event_subject_ << monitor::event("profiler/time") % tick_timer_.elapsed() % fps_;
+ if (frame_buffer_.empty())
+ {
+ return false;
+ }
+ else
+ {
+ output_buffer_.push(std::move(frame_buffer_.front()));
+ frame_buffer_.pop();
+ return true;
+ }
+ }
- output_buffer_.push(std::move(frame_buffer_.front()));
- frame_buffer_.pop();
+ core::draw_frame render_frame(bool allow_faster_rendering)
+ {
+ double sync;
+
+ if (allow_faster_rendering)
+ {
+ double ratio = std::min(
+ 1.0,
+ static_cast<double>(output_buffer_.size())
+ / static_cast<double>(std::max(1, buffer_size_ - 1)));
+ sync = 2 * ratio - ratio * ratio;
+ }
+ else
+ {
+ sync = 1.0;
+ }
+
+ return renderer_->render_frame(sync);
}
};
-spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
+spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
{
- auto template_host = get_template_host(format_desc);
+ auto template_host = get_template_host(dependencies.format_desc);
auto filename = env::template_folder() + L"\\" + template_host.filename;
if(!boost::filesystem::exists(filename))
CASPAR_THROW_EXCEPTION(file_not_found() << boost::errinfo_file_name(u8(filename)));
- return create_destroy_proxy(spl::make_shared<flash_producer>(frame_factory, format_desc, filename, template_host.width, template_host.height));
+ return create_destroy_proxy(spl::make_shared<flash_producer>(dependencies.frame_factory, dependencies.format_desc, filename, template_host.width, template_host.height));
+}
+
+void describe_swf_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Plays flash files (.swf files).");
+ sink.syntax(L"[swf_file:string]");
+ sink.para()->text(L"Plays flash files (.swf files). The file should reside under the media folder.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> PLAY 1-10 folder/swf_file");
+}
+
+spl::shared_ptr<core::frame_producer> create_swf_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
+{
+ auto filename = env::media_folder() + L"\\" + params.at(0) + L".swf";
+
+ if (!boost::filesystem::exists(filename))
+ return core::frame_producer::empty();
+
+ swf_t::header_t header(filename);
+
+ auto producer = spl::make_shared<flash_producer>(
+ dependencies.frame_factory, dependencies.format_desc, filename, header.frame_width, header.frame_height);
+
+ producer->call({ L"start_rendering" }).get();
+
+ return create_destroy_proxy(producer);
}
std::wstring find_template(const std::wstring& template_name)