]> git.sesse.net Git - casparcg/blob - modules/flash/producer/flash_producer.cpp
[general] #598 Removed all usages of asmlib, because it is worse performing than...
[casparcg] / modules / flash / producer / flash_producer.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
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.
10 *
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.
15 *
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/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "../stdafx.h"
23
24 #if defined(_MSC_VER)
25 #pragma warning (disable : 4146)
26 #pragma warning (disable : 4244)
27 #endif
28
29 #include "flash_producer.h"
30 #include "FlashAxContainer.h"
31
32 #include "../util/swf.h"
33
34 #include <core/video_format.h>
35
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>
45
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>
52
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>
58
59 #include <tbb/spin_mutex.h>
60
61 #include <functional>
62
63 namespace caspar { namespace flash {
64                 
65 class bitmap
66 {
67 public:
68         bitmap(int width, int height)
69                 : bmp_data_(nullptr)
70                 , hdc_(CreateCompatibleDC(0), DeleteDC)
71         {       
72                 BITMAPINFO info;
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);
80
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()); 
83
84                 if(!bmp_data_)
85                         CASPAR_THROW_EXCEPTION(bad_alloc());
86         }
87
88         operator HDC() {return static_cast<HDC>(hdc_.get());}
89
90         BYTE* data() { return bmp_data_;}
91         const BYTE* data() const { return bmp_data_;}
92
93 private:
94         BYTE* bmp_data_;        
95         std::shared_ptr<void> hdc_;
96         std::shared_ptr<void> bmp_;
97 };
98
99 struct template_host
100 {
101         std::wstring  video_mode;
102         std::wstring  filename;
103         int                       width;
104         int                       height;
105 };
106
107 template_host get_template_host(const core::video_format_desc& desc)
108 {
109         try
110         {
111                 std::vector<template_host> template_hosts;
112                 auto template_hosts_element = env::properties().get_child_optional(
113                         L"configuration.template-hosts");
114
115                 if (template_hosts_element)
116                 {
117                         for (auto& xml_mapping : *template_hosts_element)
118                         {
119                                 try
120                                 {
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);
127                                 }
128                                 catch(...){}
129                         }
130                 }
131
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"";});
135
136                 if(template_host_it != template_hosts.end())
137                         return *template_host_it;
138         }
139         catch(...){}
140                 
141         template_host template_host;
142         template_host.filename = L"cg.fth";
143
144         for(auto it = boost::filesystem::directory_iterator(env::template_folder()); it != boost::filesystem::directory_iterator(); ++it)
145         {
146                 if(boost::iequals(it->path().extension().wstring(), L"." + desc.name))
147                 {
148                         template_host.filename = it->path().filename().wstring();
149                         break;
150                 }
151         }
152
153         template_host.width =  desc.square_width;
154         template_host.height = desc.square_height;
155         return template_host;
156 }
157
158 boost::mutex& get_global_init_destruct_mutex()
159 {
160         static boost::mutex m;
161
162         return m;
163 }
164
165 class flash_renderer
166 {       
167         struct com_init
168         {
169                 HRESULT result_ = CoInitialize(nullptr);
170
171                 com_init()
172                 {
173                         if(FAILED(result_))
174                                 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info("Failed to initialize com-context for flash-player"));
175                 }
176
177                 ~com_init()
178                 {
179                         if(SUCCEEDED(result_))
180                                 ::CoUninitialize();
181                 }
182         } com_init_;
183
184         core::monitor::subject&                                                 monitor_subject_;
185
186         const std::wstring                                                              filename_;
187         const int                                                                               width_;
188         const int                                                                               height_;
189
190         const std::shared_ptr<core::frame_factory>              frame_factory_;
191         
192         CComObject<caspar::flash::FlashAxContainer>*    ax_                                     = nullptr;
193         core::draw_frame                                                                head_                           = core::draw_frame::late();
194         bitmap                                                                                  bmp_                            { width_, height_ };
195         prec_timer                                                                              timer_;
196         caspar::timer                                                                   tick_timer_;
197         
198         spl::shared_ptr<diagnostics::graph>                             graph_;
199         
200 public:
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)
203                 , graph_(graph)
204                 , filename_(filename)
205                 , width_(width)
206                 , height_(height)
207                 , frame_factory_(frame_factory)
208                 , bmp_(width, height)
209         {               
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));
213                 
214                 if(FAILED(CComObject<caspar::flash::FlashAxContainer>::CreateInstance(&ax_)))
215                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to create FlashAxContainer"));
216                 
217                 if(FAILED(ax_->CreateAxControl()))
218                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Create FlashAxControl"));
219
220                 ax_->set_print([this]{return print(); });
221
222                 CComPtr<IShockwaveFlash> spFlash;
223                 if(FAILED(ax_->QueryControl(&spFlash)))
224                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to Query FlashAxControl"));
225                                                                                                 
226                 if(FAILED(spFlash->put_Playing(true)) )
227                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to start playing Flash"));
228
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(), [&]
233                 {
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"));
236                 });
237                                                                                 
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"));
240                                                 
241                 ax_->SetSize(width_, height_);
242                 render_frame(0.0);
243
244                 CASPAR_LOG(info) << print() << L" Initialized.";
245         }
246
247         ~flash_renderer()
248         {               
249                 if(ax_)
250                 {
251                         ax_->DestroyAxControl();
252                         ax_->Release();
253                 }
254                 graph_->set_value("tick-time", 0.0f);
255                 graph_->set_value("frame-time", 0.0f);
256                 CASPAR_LOG(info) << print() << L" Uninitialized.";
257         }
258         
259         std::wstring call(const std::wstring& param)
260         {               
261                 std::wstring result;
262
263                 CASPAR_LOG(debug) << print() << " Call: " << param;
264
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)));
267
268                 if (boost::starts_with(result, L"<exception>"))
269                         CASPAR_LOG(warning) << print() << L" Flash call failed:" << result;
270
271                 graph_->set_tag(diagnostics::tag_severity::INFO, "param");
272
273                 return result;
274         }
275         
276         core::draw_frame render_frame(double sync)
277         {                       
278                 const float frame_time = 1.0f/fps();
279
280                 if (!ax_->IsReadyToRender())
281                         return head_;
282
283                 if (is_empty())
284                         return core::draw_frame::empty();
285
286                 if (sync > 0.00001)
287                         timer_.tick(frame_time*sync); // This will block the thread.
288
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;
292                 ax_->Tick();
293
294                 if (ax_->InvalidRect())
295                 {                       
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());
299
300                         std::memset(bmp_.data(), 0, width_ * height_ * 4);
301                         ax_->DrawControl(bmp_);
302                 
303                         std::memcpy(frame.image_data(0).begin(), bmp_.data(), width_*height_*4);
304                         head_ = core::draw_frame(std::move(frame));     
305                 }               
306
307                 MSG msg;
308                 while (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) // DO NOT REMOVE THE MESSAGE DISPATCH LOOP. Without this some stuff doesn't work!  
309                 {
310                         if (msg.message == WM_TIMER && msg.wParam == 3 && msg.lParam == 0) // We tick this inside FlashAxContainer
311                                 continue;
312
313                         TranslateMessage(&msg);
314                         DispatchMessage(&msg);
315                 }
316
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;
319                 return head_;
320         }
321         
322         bool is_empty() const
323         {
324                 return ax_->IsEmpty();
325         }
326
327         double fps() const
328         {
329                 return ax_->GetFPS();   
330         }
331         
332         std::wstring print()
333         {
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_)
337                                   + L"]";               
338         }
339 };
340
341 struct flash_producer : public core::frame_producer_base
342 {       
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_;
347         const int                                                                               width_;
348         const int                                                                               height_;
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);
351
352         tbb::atomic<int>                                                                fps_;
353
354         spl::shared_ptr<diagnostics::graph>                             graph_;
355
356         std::queue<core::draw_frame>                                    frame_buffer_;
357         tbb::concurrent_bounded_queue<core::draw_frame> output_buffer_;
358
359         core::draw_frame                                                                last_frame_                     = core::draw_frame::empty();
360                                 
361         std::unique_ptr<flash_renderer>                                 renderer_;
362         tbb::atomic<bool>                                                               has_renderer_;
363
364         executor                                                                                executor_                       = L"flash_producer";
365 public:
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)
372         {       
373                 fps_ = 0;
374          
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_);
379
380                 has_renderer_ = false;
381
382                 CASPAR_LOG(info) << print() << L" Initialized";
383         }
384
385         ~flash_producer()
386         {
387                 executor_.invoke([this]
388                 {
389                         renderer_.reset();
390                 }, task_priority::high_priority);
391         }
392
393         // frame_producer
394         
395         void log_buffered()
396         {
397                 double buffered = output_buffer_.size();
398                 auto ratio = buffered / buffer_size_;
399                 graph_->set_value("buffered", ratio);
400         }
401
402         core::draw_frame receive_impl() override
403         {                                       
404                 auto frame = last_frame_;
405
406                 double buffered = output_buffer_.size();
407                 auto ratio = buffered / buffer_size_;
408                 graph_->set_value("buffered", ratio);
409                 
410                 if (output_buffer_.try_pop(frame))
411                         last_frame_ = frame;
412                 else            
413                         graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame");
414
415                 fill_buffer();
416                                 
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_;
422
423                 return frame;
424         }
425
426         core::constraints& pixel_constraints() override
427         {
428                 return constraints_;
429         }
430                 
431         std::future<std::wstring> call(const std::vector<std::wstring>& params) override
432         {
433                 auto param = boost::algorithm::join(params, L" ");
434
435                 if (param == L"?")
436                         return make_ready_future(std::wstring(has_renderer_ ? L"1" : L"0"));
437
438                 return executor_.begin_invoke([this, param]() -> std::wstring
439                 {                       
440                         try
441                         {
442                                 bool initialize_renderer = !renderer_;
443
444                                 if (initialize_renderer)
445                                 {
446                                         renderer_.reset(new flash_renderer(monitor_subject_, graph_, frame_factory_, filename_, width_, height_));
447
448                                         has_renderer_ = true;
449                                 }
450
451                                 std::wstring result = param == L"start_rendering"
452                                                 ? L"" : renderer_->call(param);
453
454                                 if (initialize_renderer)
455                                 {
456                                         do_fill_buffer(true);
457                                 }
458
459                                 return result;  
460                         }
461                         catch(...)
462                         {
463                                 CASPAR_LOG_CURRENT_EXCEPTION();
464                                 renderer_.reset(nullptr);
465                                 has_renderer_ = false;
466                         }
467
468                         return L"";
469                 }, task_priority::high_priority);
470         }
471                 
472         std::wstring print() const override
473         { 
474                 return L"flash[" + boost::filesystem::path(filename_).wstring() + L"|" + boost::lexical_cast<std::wstring>(fps_) + L"]";                
475         }       
476
477         std::wstring name() const override
478         {
479                 return L"flash";
480         }
481
482         boost::property_tree::wptree info() const override
483         {
484                 boost::property_tree::wptree info;
485                 info.add(L"type", L"flash");
486                 return info;
487         }
488
489         core::monitor::subject& monitor_output()
490         {
491                 return monitor_subject_;
492         }
493
494         // flash_producer
495         
496         void fill_buffer()
497         {
498                 if (executor_.size() > 0)
499                         return;
500
501                 executor_.begin_invoke([this]
502                 {
503                         do_fill_buffer(false);
504                 });
505         }
506
507         void do_fill_buffer(bool initial_buffer_fill)
508         {
509                 int nothing_rendered = 0;
510                 const int MAX_NOTHING_RENDERED_RETRIES = 4;
511
512                 auto to_render = buffer_size_ - output_buffer_.size();
513                 bool allow_faster_rendering = !initial_buffer_fill;
514                 int rendered = 0;
515
516                 while (rendered < to_render)
517                 {
518                         bool was_rendered = next(allow_faster_rendering);
519                         log_buffered();
520
521                         if (was_rendered)
522                         {
523                                 ++rendered;
524                         }
525                         else
526                         {
527                                 if (nothing_rendered++ < MAX_NOTHING_RENDERED_RETRIES)
528                                 {
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();
532                                 }
533                                 else
534                                         return;
535                         }
536
537                         executor_.yield(task_priority::high_priority);
538                 }
539         }
540
541         bool next(bool allow_faster_rendering)
542         {
543                 if (!renderer_)
544                         frame_buffer_.push(core::draw_frame::empty());
545
546                 if (frame_buffer_.empty())
547                 {
548                         if (abs(renderer_->fps() / 2.0 - format_desc_.fps) < 2.0) // flash == 2 * format -> interlace
549                         {
550                                 auto frame1 = render_frame(allow_faster_rendering);
551
552                                 if (frame1 != core::draw_frame::late())
553                                 {
554                                         auto frame2 = render_frame(allow_faster_rendering);
555                                         frame_buffer_.push(core::draw_frame::interlace(frame1, frame2, format_desc_.field_mode));
556                                 }
557                         }
558                         else if (abs(renderer_->fps() - format_desc_.fps / 2.0) < 2.0) // format == 2 * flash -> duplicate
559                         {
560                                 auto frame = render_frame(allow_faster_rendering);
561
562                                 if (frame != core::draw_frame::late())
563                                 {
564                                         frame_buffer_.push(frame);
565                                         frame_buffer_.push(frame);
566                                 }
567                         }
568                         else //if(abs(renderer_->fps() - format_desc_.fps) < 0.1) // format == flash -> simple
569                         {
570                                 auto frame = render_frame(allow_faster_rendering);
571
572                                 if (frame != core::draw_frame::late())
573                                         frame_buffer_.push(frame);
574                         }
575
576                         fps_.fetch_and_store(static_cast<int>(renderer_->fps()*100.0));
577                         graph_->set_text(print());
578
579                         if (renderer_->is_empty())
580                         {
581                                 renderer_.reset();
582                                 has_renderer_ = false;
583                         }
584                 }
585
586                 if (frame_buffer_.empty())
587                 {
588                         return false;
589                 }
590                 else
591                 {
592                         output_buffer_.push(std::move(frame_buffer_.front()));
593                         frame_buffer_.pop();
594                         return true;
595                 }
596         }
597
598         core::draw_frame render_frame(bool allow_faster_rendering)
599         {
600                 double sync;
601
602                 if (allow_faster_rendering)
603                 {
604                         double ratio = std::min(
605                                         1.0,
606                                         static_cast<double>(output_buffer_.size())
607                                                         / static_cast<double>(std::max(1, buffer_size_ - 1)));
608                         sync = 2 * ratio - ratio * ratio;
609                 }
610                 else
611                 {
612                         sync = 1.0;
613                 }
614
615                 return renderer_->render_frame(sync);
616         }
617 };
618
619 spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
620 {
621         auto template_host = get_template_host(dependencies.format_desc);
622         
623         auto filename = env::template_folder() + L"\\" + template_host.filename;
624         
625         if(!boost::filesystem::exists(filename))
626                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not open flash movie " + filename));        
627
628         return create_destroy_proxy(spl::make_shared<flash_producer>(dependencies.frame_factory, dependencies.format_desc, filename, template_host.width, template_host.height));
629 }
630
631 void describe_swf_producer(core::help_sink& sink, const core::help_repository& repo)
632 {
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");
638 }
639
640 spl::shared_ptr<core::frame_producer> create_swf_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
641 {
642         auto filename = env::media_folder() + L"\\" + params.at(0) + L".swf";
643
644         if (!boost::filesystem::exists(filename))
645                 return core::frame_producer::empty();
646
647         swf_t::header_t header(filename);
648
649         auto producer = spl::make_shared<flash_producer>(
650                         dependencies.frame_factory, dependencies.format_desc, filename, header.frame_width, header.frame_height);
651
652         producer->call({ L"start_rendering" }).get();
653
654         return create_destroy_proxy(producer);
655 }
656
657 std::wstring find_template(const std::wstring& template_name)
658 {
659         if(boost::filesystem::exists(template_name + L".ft")) 
660                 return template_name + L".ft";
661         
662         if(boost::filesystem::exists(template_name + L".ct"))
663                 return template_name + L".ct";
664         
665         if(boost::filesystem::exists(template_name + L".swf"))
666                 return template_name + L".swf";
667
668         return L"";
669 }
670
671 }}