]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/branches...
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*\r
2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 *  This file is part of CasparCG.\r
5 *\r
6 *    CasparCG is free software: you can redistribute it and/or modify\r
7 *    it under the terms of the GNU General Public License as published by\r
8 *    the Free Software Foundation, either version 3 of the License, or\r
9 *    (at your option) any later version.\r
10 *\r
11 *    CasparCG is distributed in the hope that it will be useful,\r
12 *    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 *    GNU General Public License for more details.\r
15 \r
16 *    You should have received a copy of the GNU General Public License\r
17 *    along with CasparCG.  If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 */\r
20 \r
21 #include "../StdAfx.h"\r
22  \r
23 #include "decklink_consumer.h"\r
24 \r
25 #include "../util/util.h"\r
26 \r
27 #include "../interop/DeckLinkAPI_h.h"\r
28 \r
29 #include <core/mixer/read_frame.h>\r
30 \r
31 #include <common/concurrency/com_context.h>\r
32 #include <common/diagnostics/graph.h>\r
33 #include <common/exception/exceptions.h>\r
34 #include <common/memory/memcpy.h>\r
35 #include <common/memory/memclr.h>\r
36 #include <common/memory/memshfl.h>\r
37 \r
38 #include <core/consumer/frame_consumer.h>\r
39 \r
40 #include <tbb/concurrent_queue.h>\r
41 #include <tbb/cache_aligned_allocator.h>\r
42 \r
43 #include <boost/circular_buffer.hpp>\r
44 #include <boost/timer.hpp>\r
45 \r
46 namespace caspar { \r
47         \r
48 struct configuration\r
49 {\r
50         size_t  device_index;\r
51         bool    embedded_audio;\r
52         bool    internal_key;\r
53         bool    low_latency;\r
54         bool    key_only;\r
55         size_t  buffer_depth;\r
56         \r
57         configuration()\r
58                 : device_index(1)\r
59                 , embedded_audio(false)\r
60                 , internal_key(false)\r
61                 , low_latency(false)\r
62                 , key_only(false)\r
63                 , buffer_depth(core::consumer_buffer_depth()){}\r
64 };\r
65 \r
66 class decklink_frame : public IDeckLinkVideoFrame\r
67 {\r
68         std::shared_ptr<core::read_frame>                                                       frame_;\r
69         const core::video_format_desc                                                           format_desc_;\r
70 \r
71         bool                                                                                                            key_only_;\r
72         std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> key_data_;\r
73 public:\r
74         decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)\r
75                 : frame_(frame)\r
76                 , format_desc_(format_desc)\r
77                 , key_only_(key_only){}\r
78         \r
79         STDMETHOD (QueryInterface(REFIID, LPVOID*))             {return E_NOINTERFACE;}\r
80         STDMETHOD_(ULONG,                       AddRef())                       {return 1;}\r
81         STDMETHOD_(ULONG,                       Release())                      {return 1;}\r
82 \r
83         STDMETHOD_(long,                        GetWidth())                     {return format_desc_.width;}        \r
84     STDMETHOD_(long,                    GetHeight())            {return format_desc_.height;}        \r
85     STDMETHOD_(long,                    GetRowBytes())          {return format_desc_.width*4;}        \r
86         STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        \r
87     STDMETHOD_(BMDFrameFlags,   GetFlags())                     {return bmdFrameFlagDefault;}\r
88         \r
89     STDMETHOD(GetBytes(void** buffer))\r
90         {\r
91                 static std::vector<uint8_t> zeros(1920*1080*4, 0);\r
92                 if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
93                 {\r
94                         *buffer = zeros.data();\r
95                         return S_OK;\r
96                 }\r
97 \r
98                 if(!key_only_)\r
99                         *buffer = const_cast<uint8_t*>(frame_->image_data().begin());\r
100                 else\r
101                 {\r
102                         if(key_data_.empty())\r
103                         {\r
104                                 key_data_.resize(frame_->image_data().size());\r
105                                 fast_memshfl(key_data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
106                                 frame_.reset();\r
107                         }\r
108                         *buffer = key_data_.data();\r
109                 }\r
110 \r
111                 return S_OK;\r
112         }\r
113         \r
114     STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
115     STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                 {return S_FALSE;}\r
116 };\r
117 \r
118 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
119 {               \r
120         const configuration                                     config_;\r
121 \r
122         CComPtr<IDeckLink>                                      decklink_;\r
123         CComQIPtr<IDeckLinkOutput>                      output_;\r
124         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
125         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
126 \r
127         tbb::spin_mutex                                         exception_mutex_;\r
128         std::exception_ptr                                      exception_;\r
129 \r
130         tbb::atomic<bool>                                       is_running_;\r
131                 \r
132         const std::wstring                                      model_name_;\r
133         const core::video_format_desc           format_desc_;\r
134         const size_t                                            buffer_size_;\r
135 \r
136         long long                                                       frames_scheduled_;\r
137         long long                                                       audio_scheduled_;\r
138 \r
139         size_t                                                          preroll_count_;\r
140                 \r
141         std::list<std::shared_ptr<IDeckLinkVideoFrame>> frame_container_; // Must be std::list in order to guarantee that pointers are always valid.\r
142         boost::circular_buffer<std::vector<int16_t>>    audio_container_;\r
143 \r
144         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;\r
145         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;\r
146         \r
147         std::shared_ptr<diagnostics::graph> graph_;\r
148         boost::timer tick_timer_;\r
149 \r
150 public:\r
151         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc) \r
152                 : config_(config)\r
153                 , decklink_(get_device(config.device_index))\r
154                 , output_(decklink_)\r
155                 , configuration_(decklink_)\r
156                 , keyer_(decklink_)\r
157                 , model_name_(get_model_name(decklink_))\r
158                 , format_desc_(format_desc)\r
159                 , buffer_size_(config.embedded_audio ? config.buffer_depth + 1 : config.buffer_depth) // Minimum buffer-size 3.\r
160                 , frames_scheduled_(0)\r
161                 , audio_scheduled_(0)\r
162                 , preroll_count_(0)\r
163                 , audio_container_(buffer_size_+1)\r
164         {\r
165                 is_running_ = true;\r
166                                 \r
167                 video_frame_buffer_.set_capacity(1);\r
168                 audio_frame_buffer_.set_capacity(1);\r
169 \r
170                 graph_ = diagnostics::create_graph(narrow(print()));\r
171                 graph_->add_guide("tick-time", 0.5);\r
172                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   \r
173                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
174                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));\r
175                 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));\r
176                 \r
177                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));\r
178                                 \r
179                 if(config.embedded_audio)\r
180                         enable_audio();\r
181 \r
182                 set_latency(config.low_latency);                                \r
183                 set_keyer(config.internal_key);\r
184                                 \r
185                 if(config.embedded_audio)               \r
186                         output_->BeginAudioPreroll();           \r
187                 \r
188                 for(size_t n = 0; n < buffer_size_; ++n)\r
189                         schedule_next_video(make_safe<core::read_frame>());\r
190 \r
191                 if(!config.embedded_audio)\r
192                         start_playback();\r
193         }\r
194 \r
195         ~decklink_consumer()\r
196         {               \r
197                 is_running_ = false;\r
198                 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
199                 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
200 \r
201                 if(output_ != nullptr) \r
202                 {\r
203                         output_->StopScheduledPlayback(0, nullptr, 0);\r
204                         if(config_.embedded_audio)\r
205                                 output_->DisableAudioOutput();\r
206                         output_->DisableVideoOutput();\r
207                 }\r
208         }\r
209                         \r
210         const core::video_format_desc& get_video_format_desc() const\r
211         {\r
212                 return format_desc_;\r
213         }\r
214 \r
215         void set_latency(bool low_latency)\r
216         {               \r
217                 if(!low_latency)\r
218                 {\r
219                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
220                         CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";\r
221                 }\r
222                 else\r
223                 {                       \r
224                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
225                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode";\r
226                 }\r
227         }\r
228 \r
229         void set_keyer(bool internal_key)\r
230         {\r
231                 if(internal_key) \r
232                 {\r
233                         if(FAILED(keyer_->Enable(FALSE)))                       \r
234                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
235                         else if(FAILED(keyer_->SetLevel(255)))                  \r
236                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
237                         else\r
238                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
239                 }\r
240                 else\r
241                 {\r
242                         if(FAILED(keyer_->Enable(TRUE)))                        \r
243                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
244                         else if(FAILED(keyer_->SetLevel(255)))                  \r
245                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
246                         else\r
247                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
248                 }\r
249         }\r
250         \r
251         void enable_audio()\r
252         {\r
253                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
254                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
255                                 \r
256                 if(FAILED(output_->SetAudioCallback(this)))\r
257                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
258 \r
259                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
260         }\r
261 \r
262         void enable_video(BMDDisplayMode display_mode)\r
263         {\r
264                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
265                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
266                 \r
267                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
268                         BOOST_THROW_EXCEPTION(caspar_exception() \r
269                                                                         << msg_info(narrow(print()) + " Failed to set playback completion callback.")\r
270                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
271         }\r
272 \r
273         void start_playback()\r
274         {\r
275                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
276                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
277         }\r
278         \r
279         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
280         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
281         STDMETHOD_(ULONG, Release())                            {return 1;}\r
282         \r
283         STDMETHOD(ScheduledPlaybackHasStopped())\r
284         {\r
285                 is_running_ = false;\r
286                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
287                 return S_OK;\r
288         }\r
289 \r
290         STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))\r
291         {\r
292                 if(!is_running_)\r
293                         return E_FAIL;\r
294                 \r
295                 try\r
296                 {\r
297                         if(result == bmdOutputFrameDisplayedLate)\r
298                         {\r
299                                 graph_->add_tag("late-frame");\r
300                                 ++frames_scheduled_;\r
301                                 ++audio_scheduled_;\r
302                         }\r
303                         else if(result == bmdOutputFrameDropped)\r
304                                 graph_->add_tag("dropped-frame");\r
305                         else if(result == bmdOutputFrameFlushed)\r
306                                 graph_->add_tag("flushed-frame");\r
307 \r
308                         frame_container_.erase(std::find_if(frame_container_.begin(), frame_container_.end(), [&](const std::shared_ptr<IDeckLinkVideoFrame>& frame)\r
309                         {\r
310                                 return frame.get() == completed_frame;\r
311                         }));\r
312 \r
313                         std::shared_ptr<core::read_frame> frame;        \r
314                         video_frame_buffer_.pop(frame);                                 \r
315                         schedule_next_video(make_safe(frame));  \r
316                 }\r
317                 catch(...)\r
318                 {\r
319                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
320                         exception_ = std::current_exception();\r
321                         return E_FAIL;\r
322                 }\r
323 \r
324                 return S_OK;\r
325         }\r
326                 \r
327         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
328         {\r
329                 if(!is_running_)\r
330                         return E_FAIL;\r
331                 \r
332                 try\r
333                 {       \r
334                         if(preroll)\r
335                         {\r
336                                 if(++preroll_count_ >= buffer_size_)\r
337                                 {\r
338                                         output_->EndAudioPreroll();\r
339                                         start_playback();                               \r
340                                 }\r
341                                 else\r
342                                         schedule_next_audio(make_safe<core::read_frame>());     \r
343                         }\r
344                         else\r
345                         {\r
346                                 std::shared_ptr<core::read_frame> frame;\r
347                                 audio_frame_buffer_.pop(frame);\r
348                                 schedule_next_audio(make_safe(frame));  \r
349                         }\r
350                 }\r
351                 catch(...)\r
352                 {\r
353                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
354                         exception_ = std::current_exception();\r
355                         return E_FAIL;\r
356                 }\r
357 \r
358                 return S_OK;\r
359         }\r
360 \r
361         void schedule_next_audio(const safe_ptr<core::read_frame>& frame)\r
362         {\r
363                 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;\r
364 \r
365                 audio_container_.push_back(std::vector<int16_t>(frame->audio_data().begin(), frame->audio_data().end()));\r
366 \r
367                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))\r
368                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
369         }\r
370                         \r
371         void schedule_next_video(const safe_ptr<core::read_frame>& frame)\r
372         {\r
373                 frame_container_.push_back(std::make_shared<decklink_frame>(frame, format_desc_, config_.key_only));\r
374                 if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))\r
375                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
376 \r
377                 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
378                 tick_timer_.restart();\r
379         }\r
380 \r
381         void send(const safe_ptr<core::read_frame>& frame)\r
382         {\r
383                 {\r
384                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
385                         if(exception_ != nullptr)\r
386                                 std::rethrow_exception(exception_);\r
387                 }\r
388 \r
389                 if(!is_running_)\r
390                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));\r
391                 \r
392                 if(config_.embedded_audio)\r
393                         audio_frame_buffer_.push(frame);        \r
394                 video_frame_buffer_.push(frame);        \r
395         }\r
396         \r
397         std::wstring print() const\r
398         {\r
399                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
400         }\r
401 };\r
402 \r
403 struct decklink_consumer_proxy : public core::frame_consumer\r
404 {\r
405         const configuration                             config_;\r
406         com_context<decklink_consumer>  context_;\r
407         core::video_format_desc                 format_desc_;\r
408         size_t                                                  fail_count_;\r
409 public:\r
410 \r
411         decklink_consumer_proxy(const configuration& config)\r
412                 : config_(config)\r
413                 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
414                 , fail_count_(0)\r
415         {\r
416         }\r
417 \r
418         ~decklink_consumer_proxy()\r
419         {\r
420                 auto str = print();\r
421                 context_.reset();\r
422                 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     \r
423         }\r
424         \r
425         virtual void initialize(const core::video_format_desc& format_desc)\r
426         {\r
427                 format_desc_ = format_desc;\r
428                 context_.reset([&]{return new decklink_consumer(config_, format_desc_);});              \r
429                                 \r
430                 CASPAR_LOG(info) << print() << L" Successfully Initialized.";   \r
431         }\r
432         \r
433         virtual bool send(const safe_ptr<core::read_frame>& frame)\r
434         {\r
435                 if(!context_)\r
436                         context_.reset([&]{return new decklink_consumer(config_, format_desc_);});\r
437 \r
438                 try\r
439                 {\r
440                         context_->send(frame);\r
441                         fail_count_ = 0;\r
442                 }\r
443                 catch(...)\r
444                 {\r
445                         context_.reset();\r
446 \r
447                         if(fail_count_++ > 3)\r
448                                 return false;  // Outside didn't handle exception properly, just give up.\r
449                         \r
450                         throw;\r
451                 }\r
452 \r
453                 return true;\r
454         }\r
455         \r
456         virtual std::wstring print() const\r
457         {\r
458                 return context_ ? context_->print() : L"decklink_consumer";\r
459         }\r
460                         \r
461         virtual const core::video_format_desc& get_video_format_desc() const\r
462         {\r
463                 return format_desc_;\r
464         }\r
465 };      \r
466 \r
467 safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params) \r
468 {\r
469         if(params.size() < 1 || params[0] != L"DECKLINK")\r
470                 return core::frame_consumer::empty();\r
471         \r
472         configuration config;\r
473                 \r
474         if(params.size() > 1)\r
475                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
476         \r
477         config.internal_key             = std::find(params.begin(), params.end(), L"INTERNAL_KEY")       != params.end();\r
478         config.low_latency              = std::find(params.begin(), params.end(), L"LOW_LATENCY")        != params.end();\r
479         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
480         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
481 \r
482         return make_safe<decklink_consumer_proxy>(config);\r
483 }\r
484 \r
485 safe_ptr<core::frame_consumer> create_decklink_consumer(const boost::property_tree::ptree& ptree) \r
486 {\r
487         configuration config;\r
488 \r
489         config.internal_key             = ptree.get("internal-key",       config.internal_key);\r
490         config.low_latency              = ptree.get("low-latency",        config.low_latency);\r
491         config.key_only                 = ptree.get("key-only",           config.key_only);\r
492         config.device_index             = ptree.get("device",             config.device_index);\r
493         config.embedded_audio   = ptree.get("embedded-audio", config.embedded_audio);\r
494 \r
495         return make_safe<decklink_consumer_proxy>(config);\r
496 }\r
497 \r
498 }\r
499 \r
500 /*\r
501 ##############################################################################\r
502 Pre-rolling\r
503 \r
504 Mail: 2011-05-09\r
505 \r
506 Yoshan\r
507 BMD Developer Support\r
508 developer@blackmagic-design.com\r
509 \r
510 -----------------------------------------------------------------------------\r
511 \r
512 Thanks for your inquiry. The minimum number of frames that you can preroll \r
513 for scheduled playback is three frames for video and four frames for audio. \r
514 As you mentioned if you preroll less frames then playback will not start or\r
515 playback will be very sporadic. From our experience with Media Express, we \r
516 recommended that at least seven frames are prerolled for smooth playback. \r
517 \r
518 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
519 There can be around 3 frames worth of latency on scheduled output.\r
520 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
521 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
522 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
523 guarantee that the provided frame will be output as soon the previous \r
524 frame output has been completed.\r
525 ################################################################################\r
526 */\r
527 \r
528 /*\r
529 ##############################################################################\r
530 Async DMA Transfer without redundant copying\r
531 \r
532 Mail: 2011-05-10\r
533 \r
534 Yoshan\r
535 BMD Developer Support\r
536 developer@blackmagic-design.com\r
537 \r
538 -----------------------------------------------------------------------------\r
539 \r
540 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
541 and providing a pointer to your video buffer when GetBytes() is called. \r
542 This may help to keep copying to a minimum. Please ensure that the pixel \r
543 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
544 have to colourspace convert which may result in additional copying.\r
545 ################################################################################\r
546 */