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