2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Robert Nagy, ronag89@gmail.com
22 #include "../stdafx.h"
25 #pragma warning (disable : 4146)
26 #pragma warning (disable : 4244)
29 #include "flash_producer.h"
30 #include "FlashAxContainer.h"
32 #include "../util/swf.h"
34 #include <core/video_format.h>
36 #include <core/frame/frame.h>
37 #include <core/frame/draw_frame.h>
38 #include <core/frame/frame_factory.h>
39 #include <core/frame/pixel_format.h>
40 #include <core/frame/audio_channel_layout.h>
41 #include <core/producer/frame_producer.h>
42 #include <core/monitor/monitor.h>
43 #include <core/help/help_repository.h>
44 #include <core/help/help_sink.h>
46 #include <common/env.h>
47 #include <common/executor.h>
48 #include <common/lock.h>
49 #include <common/diagnostics/graph.h>
50 #include <common/prec_timer.h>
51 #include <common/array.h>
53 #include <boost/filesystem.hpp>
54 #include <boost/property_tree/ptree.hpp>
55 #include <boost/thread.hpp>
56 #include <boost/timer.hpp>
57 #include <boost/algorithm/string.hpp>
59 #include <tbb/spin_mutex.h>
63 namespace caspar { namespace flash {
68 bitmap(int width, int height)
70 , hdc_(CreateCompatibleDC(0), DeleteDC)
73 memset(&info, 0, sizeof(BITMAPINFO));
74 info.bmiHeader.biBitCount = 32;
75 info.bmiHeader.biCompression = BI_RGB;
76 info.bmiHeader.biHeight = static_cast<LONG>(-height);
77 info.bmiHeader.biPlanes = 1;
78 info.bmiHeader.biSize = sizeof(BITMAPINFO);
79 info.bmiHeader.biWidth = static_cast<LONG>(width);
81 bmp_.reset(CreateDIBSection(static_cast<HDC>(hdc_.get()), &info, DIB_RGB_COLORS, reinterpret_cast<void**>(&bmp_data_), 0, 0), DeleteObject);
82 SelectObject(static_cast<HDC>(hdc_.get()), bmp_.get());
85 CASPAR_THROW_EXCEPTION(bad_alloc());
88 operator HDC() {return static_cast<HDC>(hdc_.get());}
90 BYTE* data() { return bmp_data_;}
91 const BYTE* data() const { return bmp_data_;}
95 std::shared_ptr<void> hdc_;
96 std::shared_ptr<void> bmp_;
101 std::wstring video_mode;
102 std::wstring filename;
107 template_host get_template_host(const core::video_format_desc& desc)
111 std::vector<template_host> template_hosts;
112 auto template_hosts_element = env::properties().get_child_optional(
113 L"configuration.template-hosts");
115 if (template_hosts_element)
117 for (auto& xml_mapping : *template_hosts_element)
121 template_host template_host;
122 template_host.video_mode = xml_mapping.second.get(L"video-mode", L"");
123 template_host.filename = xml_mapping.second.get(L"filename", L"cg.fth");
124 template_host.width = xml_mapping.second.get(L"width", desc.width);
125 template_host.height = xml_mapping.second.get(L"height", desc.height);
126 template_hosts.push_back(template_host);
132 auto template_host_it = boost::find_if(template_hosts, [&](template_host template_host){return template_host.video_mode == desc.name;});
133 if(template_host_it == template_hosts.end())
134 template_host_it = boost::find_if(template_hosts, [&](template_host template_host){return template_host.video_mode == L"";});
136 if(template_host_it != template_hosts.end())
137 return *template_host_it;
141 template_host template_host;
142 template_host.filename = L"cg.fth";
144 for(auto it = boost::filesystem::directory_iterator(env::template_folder()); it != boost::filesystem::directory_iterator(); ++it)
146 if(boost::iequals(it->path().extension().wstring(), L"." + desc.name))
148 template_host.filename = it->path().filename().wstring();
153 template_host.width = desc.square_width;
154 template_host.height = desc.square_height;
155 return template_host;
158 boost::mutex& get_global_init_destruct_mutex()
160 static boost::mutex m;
169 HRESULT result_ = CoInitialize(nullptr);
174 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info("Failed to initialize com-context for flash-player"));
179 if(SUCCEEDED(result_))
184 core::monitor::subject& monitor_subject_;
186 const std::wstring filename_;
190 const std::shared_ptr<core::frame_factory> frame_factory_;
192 CComObject<caspar::flash::FlashAxContainer>* ax_ = nullptr;
193 core::draw_frame head_ = core::draw_frame::late();
194 bitmap bmp_ { width_, height_ };
196 caspar::timer tick_timer_;
198 spl::shared_ptr<diagnostics::graph> graph_;
201 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)
202 : monitor_subject_(monitor_subject)
204 , filename_(filename)
207 , frame_factory_(frame_factory)
208 , bmp_(width, height)
210 graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));
211 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
212 graph_->set_color("param", diagnostics::color(1.0f, 0.5f, 0.0f));
214 if(FAILED(CComObject<caspar::flash::FlashAxContainer>::CreateInstance(&ax_)))
215 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to create FlashAxContainer"));
217 if(FAILED(ax_->CreateAxControl()))
218 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Create FlashAxControl"));
220 ax_->set_print([this]{return print(); });
222 CComPtr<IShockwaveFlash> spFlash;
223 if(FAILED(ax_->QueryControl(&spFlash)))
224 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Query FlashAxControl"));
226 if(FAILED(spFlash->put_Playing(true)) )
227 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to start playing Flash"));
229 // Concurrent initialization of two template hosts causes a
230 // SecurityException later when CG ADD is performed. Initialization is
231 // therefore serialized via a global mutex.
232 lock(get_global_init_destruct_mutex(), [&]
234 if (FAILED(spFlash->put_Movie(CComBSTR(filename.c_str()))))
235 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Load Template Host"));
238 if(FAILED(spFlash->put_ScaleMode(2))) //Exact fit. Scale without respect to the aspect ratio.
239 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Set Scale Mode"));
241 ax_->SetSize(width_, height_);
244 CASPAR_LOG(info) << print() << L" Initialized.";
251 ax_->DestroyAxControl();
254 graph_->set_value("tick-time", 0.0f);
255 graph_->set_value("frame-time", 0.0f);
256 CASPAR_LOG(info) << print() << L" Uninitialized.";
259 std::wstring call(const std::wstring& param)
263 CASPAR_LOG(debug) << print() << " Call: " << param;
265 if(!ax_->FlashCall(param, result))
266 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)));
268 if (boost::starts_with(result, L"<exception>"))
269 CASPAR_LOG(warning) << print() << L" Flash call failed:" << result;
271 graph_->set_tag(diagnostics::tag_severity::INFO, "param");
276 core::draw_frame render_frame(double sync)
278 const float frame_time = 1.0f/fps();
280 if (!ax_->IsReadyToRender())
284 return core::draw_frame::empty();
287 timer_.tick(frame_time*sync); // This will block the thread.
289 graph_->set_value("tick-time", static_cast<float>(tick_timer_.elapsed() / frame_time) * 0.5f);
290 tick_timer_.restart();
291 boost::timer frame_timer;
294 if (ax_->InvalidRect())
296 core::pixel_format_desc desc = core::pixel_format::bgra;
297 desc.planes.push_back(core::pixel_format_desc::plane(width_, height_, 4));
298 auto frame = frame_factory_->create_frame(this, desc, core::audio_channel_layout::invalid());
300 std::memset(bmp_.data(), 0, width_ * height_ * 4);
301 ax_->DrawControl(bmp_);
303 std::memcpy(frame.image_data(0).begin(), bmp_.data(), width_*height_*4);
304 head_ = core::draw_frame(std::move(frame));
308 while (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) // DO NOT REMOVE THE MESSAGE DISPATCH LOOP. Without this some stuff doesn't work!
310 if (msg.message == WM_TIMER && msg.wParam == 3 && msg.lParam == 0) // We tick this inside FlashAxContainer
313 TranslateMessage(&msg);
314 DispatchMessage(&msg);
317 graph_->set_value("frame-time", static_cast<float>(frame_timer.elapsed()/frame_time)*0.5f);
318 monitor_subject_ << core::monitor::message("/renderer/profiler/time") % frame_timer.elapsed() % frame_time;
322 bool is_empty() const
324 return ax_->IsEmpty();
329 return ax_->GetFPS();
334 return L"flash-player[" + boost::filesystem::path(filename_).filename().wstring()
335 + L"|" + boost::lexical_cast<std::wstring>(width_)
336 + L"x" + boost::lexical_cast<std::wstring>(height_)
341 struct flash_producer : public core::frame_producer_base
343 core::monitor::subject monitor_subject_;
344 const std::wstring filename_;
345 const spl::shared_ptr<core::frame_factory> frame_factory_;
346 const core::video_format_desc format_desc_;
349 core::constraints constraints_ { static_cast<double>(width_), static_cast<double>(height_) };
350 const int buffer_size_ = env::properties().get(L"configuration.flash.buffer-depth", format_desc_.fps > 30.0 ? 4 : 2);
352 tbb::atomic<int> fps_;
354 spl::shared_ptr<diagnostics::graph> graph_;
356 std::queue<core::draw_frame> frame_buffer_;
357 tbb::concurrent_bounded_queue<core::draw_frame> output_buffer_;
359 core::draw_frame last_frame_ = core::draw_frame::empty();
361 std::unique_ptr<flash_renderer> renderer_;
362 tbb::atomic<bool> has_renderer_;
364 executor executor_ = L"flash_producer";
366 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)
367 : filename_(filename)
368 , frame_factory_(frame_factory)
369 , format_desc_(format_desc)
370 , width_(width > 0 ? width : format_desc.width)
371 , height_(height > 0 ? height : format_desc.height)
375 graph_->set_color("buffered", diagnostics::color(1.0f, 1.0f, 0.0f));
376 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.9f));
377 graph_->set_text(print());
378 diagnostics::register_graph(graph_);
380 has_renderer_ = false;
382 CASPAR_LOG(info) << print() << L" Initialized";
387 executor_.invoke([this]
390 }, task_priority::high_priority);
397 double buffered = output_buffer_.size();
398 auto ratio = buffered / buffer_size_;
399 graph_->set_value("buffered", ratio);
402 core::draw_frame receive_impl() override
404 auto frame = last_frame_;
406 double buffered = output_buffer_.size();
407 auto ratio = buffered / buffer_size_;
408 graph_->set_value("buffered", ratio);
410 if (output_buffer_.try_pop(frame))
413 graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame");
417 monitor_subject_ << core::monitor::message("/host/path") % filename_
418 << core::monitor::message("/host/width") % width_
419 << core::monitor::message("/host/height") % height_
420 << core::monitor::message("/host/fps") % fps_
421 << core::monitor::message("/buffer") % output_buffer_.size() % buffer_size_;
426 core::constraints& pixel_constraints() override
431 std::future<std::wstring> call(const std::vector<std::wstring>& params) override
433 auto param = boost::algorithm::join(params, L" ");
436 return make_ready_future(std::wstring(has_renderer_ ? L"1" : L"0"));
438 return executor_.begin_invoke([this, param]() -> std::wstring
442 bool initialize_renderer = !renderer_;
444 if (initialize_renderer)
446 renderer_.reset(new flash_renderer(monitor_subject_, graph_, frame_factory_, filename_, width_, height_));
448 has_renderer_ = true;
451 std::wstring result = param == L"start_rendering"
452 ? L"" : renderer_->call(param);
454 if (initialize_renderer)
456 do_fill_buffer(true);
463 CASPAR_LOG_CURRENT_EXCEPTION();
464 renderer_.reset(nullptr);
465 has_renderer_ = false;
469 }, task_priority::high_priority);
472 std::wstring print() const override
474 return L"flash[" + boost::filesystem::path(filename_).wstring() + L"|" + boost::lexical_cast<std::wstring>(fps_) + L"]";
477 std::wstring name() const override
482 boost::property_tree::wptree info() const override
484 boost::property_tree::wptree info;
485 info.add(L"type", L"flash");
489 core::monitor::subject& monitor_output()
491 return monitor_subject_;
498 if (executor_.size() > 0)
501 executor_.begin_invoke([this]
503 do_fill_buffer(false);
507 void do_fill_buffer(bool initial_buffer_fill)
509 int nothing_rendered = 0;
510 const int MAX_NOTHING_RENDERED_RETRIES = 4;
512 auto to_render = buffer_size_ - output_buffer_.size();
513 bool allow_faster_rendering = !initial_buffer_fill;
516 while (rendered < to_render)
518 bool was_rendered = next(allow_faster_rendering);
527 if (nothing_rendered++ < MAX_NOTHING_RENDERED_RETRIES)
529 // Flash player not ready with first frame, sleep to not busy-loop;
530 boost::this_thread::sleep(boost::posix_time::milliseconds(10));
531 boost::this_thread::yield();
537 executor_.yield(task_priority::high_priority);
541 bool next(bool allow_faster_rendering)
544 frame_buffer_.push(core::draw_frame::empty());
546 if (frame_buffer_.empty())
548 if (abs(renderer_->fps() / 2.0 - format_desc_.fps) < 2.0) // flash == 2 * format -> interlace
550 auto frame1 = render_frame(allow_faster_rendering);
552 if (frame1 != core::draw_frame::late())
554 auto frame2 = render_frame(allow_faster_rendering);
555 frame_buffer_.push(core::draw_frame::interlace(frame1, frame2, format_desc_.field_mode));
558 else if (abs(renderer_->fps() - format_desc_.fps / 2.0) < 2.0) // format == 2 * flash -> duplicate
560 auto frame = render_frame(allow_faster_rendering);
562 if (frame != core::draw_frame::late())
564 frame_buffer_.push(frame);
565 frame_buffer_.push(frame);
568 else //if(abs(renderer_->fps() - format_desc_.fps) < 0.1) // format == flash -> simple
570 auto frame = render_frame(allow_faster_rendering);
572 if (frame != core::draw_frame::late())
573 frame_buffer_.push(frame);
576 fps_.fetch_and_store(static_cast<int>(renderer_->fps()*100.0));
577 graph_->set_text(print());
579 if (renderer_->is_empty())
582 has_renderer_ = false;
586 if (frame_buffer_.empty())
592 output_buffer_.push(std::move(frame_buffer_.front()));
598 core::draw_frame render_frame(bool allow_faster_rendering)
602 if (allow_faster_rendering)
604 double ratio = std::min(
606 static_cast<double>(output_buffer_.size())
607 / static_cast<double>(std::max(1, buffer_size_ - 1)));
608 sync = 2 * ratio - ratio * ratio;
615 return renderer_->render_frame(sync);
619 spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
621 auto template_host = get_template_host(dependencies.format_desc);
623 auto filename = env::template_folder() + L"\\" + template_host.filename;
625 if(!boost::filesystem::exists(filename))
626 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not open flash movie " + filename));
628 return create_destroy_proxy(spl::make_shared<flash_producer>(dependencies.frame_factory, dependencies.format_desc, filename, template_host.width, template_host.height));
631 void describe_swf_producer(core::help_sink& sink, const core::help_repository& repo)
633 sink.short_description(L"Plays flash files (.swf files).");
634 sink.syntax(L"[swf_file:string]");
635 sink.para()->text(L"Plays flash files (.swf files). The file should reside under the media folder.");
636 sink.para()->text(L"Examples:");
637 sink.example(L">> PLAY 1-10 folder/swf_file");
640 spl::shared_ptr<core::frame_producer> create_swf_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
642 auto filename = env::media_folder() + L"\\" + params.at(0) + L".swf";
644 if (!boost::filesystem::exists(filename))
645 return core::frame_producer::empty();
647 swf_t::header_t header(filename);
649 auto producer = spl::make_shared<flash_producer>(
650 dependencies.frame_factory, dependencies.format_desc, filename, header.frame_width, header.frame_height);
652 producer->call({ L"start_rendering" }).get();
654 return create_destroy_proxy(producer);
657 std::wstring find_template(const std::wstring& template_name)
659 if(boost::filesystem::exists(template_name + L".ft"))
660 return template_name + L".ft";
662 if(boost::filesystem::exists(template_name + L".ct"))
663 return template_name + L".ct";
665 if(boost::filesystem::exists(template_name + L".swf"))
666 return template_name + L".swf";