]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
2.0.2: Intergrated channel-exp branch:
[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_text(print());\r
190                 diagnostics::register_graph(graph_);\r
191                 \r
192                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));\r
193                                 \r
194                 if(config.embedded_audio)\r
195                         enable_audio();\r
196 \r
197                 set_latency(config.low_latency);                                \r
198                 set_keyer(config.internal_key);\r
199                                 \r
200                 if(config.embedded_audio)               \r
201                         output_->BeginAudioPreroll();           \r
202                 \r
203                 for(size_t n = 0; n < buffer_size_; ++n)\r
204                         schedule_next_video(make_safe<core::read_frame>());\r
205 \r
206                 if(!config.embedded_audio)\r
207                         start_playback();\r
208         }\r
209 \r
210         ~decklink_consumer()\r
211         {               \r
212                 is_running_ = false;\r
213                 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
214                 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
215 \r
216                 if(output_ != nullptr) \r
217                 {\r
218                         output_->StopScheduledPlayback(0, nullptr, 0);\r
219                         if(config_.embedded_audio)\r
220                                 output_->DisableAudioOutput();\r
221                         output_->DisableVideoOutput();\r
222                 }\r
223         }\r
224                         \r
225         const core::video_format_desc& get_video_format_desc() const\r
226         {\r
227                 return format_desc_;\r
228         }\r
229 \r
230         void set_latency(bool low_latency)\r
231         {               \r
232                 if(!low_latency)\r
233                 {\r
234                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
235                         CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";\r
236                 }\r
237                 else\r
238                 {                       \r
239                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
240                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode";\r
241                 }\r
242         }\r
243 \r
244         void set_keyer(bool internal_key)\r
245         {\r
246                 if(internal_key) \r
247                 {\r
248                         if(FAILED(keyer_->Enable(FALSE)))                       \r
249                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
250                         else if(FAILED(keyer_->SetLevel(255)))                  \r
251                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
252                         else\r
253                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
254                 }\r
255                 else\r
256                 {\r
257                         if(FAILED(keyer_->Enable(TRUE)))                        \r
258                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
259                         else if(FAILED(keyer_->SetLevel(255)))                  \r
260                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
261                         else\r
262                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
263                 }\r
264         }\r
265         \r
266         void enable_audio()\r
267         {\r
268                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
269                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
270                                 \r
271                 if(FAILED(output_->SetAudioCallback(this)))\r
272                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
273 \r
274                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
275         }\r
276 \r
277         void enable_video(BMDDisplayMode display_mode)\r
278         {\r
279                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
280                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
281                 \r
282                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
283                         BOOST_THROW_EXCEPTION(caspar_exception() \r
284                                                                         << msg_info(narrow(print()) + " Failed to set playback completion callback.")\r
285                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
286         }\r
287 \r
288         void start_playback()\r
289         {\r
290                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
291                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
292         }\r
293         \r
294         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
295         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
296         STDMETHOD_(ULONG, Release())                            {return 1;}\r
297         \r
298         STDMETHOD(ScheduledPlaybackHasStopped())\r
299         {\r
300                 is_running_ = false;\r
301                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
302                 return S_OK;\r
303         }\r
304 \r
305         STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))\r
306         {\r
307                 if(!is_running_)\r
308                         return E_FAIL;\r
309                 \r
310                 try\r
311                 {\r
312                         if(result == bmdOutputFrameDisplayedLate)\r
313                         {\r
314                                 graph_->add_tag("late-frame");\r
315                                 ++frames_scheduled_;\r
316                                 ++audio_scheduled_;\r
317                         }\r
318                         else if(result == bmdOutputFrameDropped)\r
319                                 graph_->add_tag("dropped-frame");\r
320                         else if(result == bmdOutputFrameFlushed)\r
321                                 graph_->add_tag("flushed-frame");\r
322 \r
323                         std::shared_ptr<core::read_frame> frame;        \r
324                         video_frame_buffer_.pop(frame);                                 \r
325                         schedule_next_video(make_safe_ptr(frame));      \r
326                 }\r
327                 catch(...)\r
328                 {\r
329                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
330                         exception_ = std::current_exception();\r
331                         return E_FAIL;\r
332                 }\r
333 \r
334                 return S_OK;\r
335         }\r
336                 \r
337         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
338         {\r
339                 if(!is_running_)\r
340                         return E_FAIL;\r
341                 \r
342                 try\r
343                 {       \r
344                         if(preroll)\r
345                         {\r
346                                 if(++preroll_count_ >= buffer_size_)\r
347                                 {\r
348                                         output_->EndAudioPreroll();\r
349                                         start_playback();                               \r
350                                 }\r
351                                 else\r
352                                         schedule_next_audio(make_safe<core::read_frame>());     \r
353                         }\r
354                         else\r
355                         {\r
356                                 std::shared_ptr<core::read_frame> frame;\r
357                                 audio_frame_buffer_.pop(frame);\r
358                                 schedule_next_audio(make_safe_ptr(frame));      \r
359                         }\r
360                 }\r
361                 catch(...)\r
362                 {\r
363                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
364                         exception_ = std::current_exception();\r
365                         return E_FAIL;\r
366                 }\r
367 \r
368                 return S_OK;\r
369         }\r
370 \r
371         void schedule_next_audio(const safe_ptr<core::read_frame>& frame)\r
372         {\r
373                 const int sample_frame_count = frame->audio_data().size()/format_desc_.audio_channels;\r
374 \r
375                 audio_container_.push_back(std::vector<int32_t>(frame->audio_data().begin(), frame->audio_data().end()));\r
376 \r
377                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, (audio_scheduled_++) * sample_frame_count, format_desc_.audio_sample_rate, nullptr)))\r
378                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
379         }\r
380                         \r
381         void schedule_next_video(const safe_ptr<core::read_frame>& frame)\r
382         {\r
383                 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));\r
384                 if(FAILED(output_->ScheduleVideoFrame(frame2, (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))\r
385                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
386 \r
387                 graph_->update_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
388                 tick_timer_.restart();\r
389         }\r
390 \r
391         void send(const safe_ptr<core::read_frame>& frame)\r
392         {\r
393                 {\r
394                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
395                         if(exception_ != nullptr)\r
396                                 std::rethrow_exception(exception_);\r
397                 }\r
398 \r
399                 if(!is_running_)\r
400                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));\r
401                 \r
402                 if(config_.embedded_audio)\r
403                         audio_frame_buffer_.push(frame);        \r
404                 video_frame_buffer_.push(frame);        \r
405         }\r
406         \r
407         std::wstring print() const\r
408         {\r
409                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
410         }\r
411 };\r
412 \r
413 struct decklink_consumer_proxy : public core::frame_consumer\r
414 {\r
415         const configuration                             config_;\r
416         com_context<decklink_consumer>  context_;\r
417         core::video_format_desc                 format_desc_;\r
418 public:\r
419 \r
420         decklink_consumer_proxy(const configuration& config)\r
421                 : config_(config)\r
422                 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
423         {\r
424         }\r
425 \r
426         ~decklink_consumer_proxy()\r
427         {\r
428                 auto str = print();\r
429                 context_.reset();\r
430                 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     \r
431         }\r
432         \r
433         virtual void initialize(const core::video_format_desc& format_desc)\r
434         {\r
435                 format_desc_ = format_desc;\r
436                 context_.reset([&]{return new decklink_consumer(config_, format_desc_);});              \r
437                                 \r
438                 CASPAR_LOG(info) << print() << L" Successfully Initialized.";   \r
439         }\r
440         \r
441         virtual bool send(const safe_ptr<core::read_frame>& frame)\r
442         {\r
443                 context_->send(frame);\r
444                 return true;\r
445         }\r
446         \r
447         virtual std::wstring print() const\r
448         {\r
449                 return context_ ? context_->print() : L"decklink_consumer";\r
450         }\r
451                         \r
452         virtual const core::video_format_desc& get_video_format_desc() const\r
453         {\r
454                 return format_desc_;\r
455         }\r
456 \r
457         virtual size_t buffer_depth() const\r
458         {\r
459                 return config_.buffer_depth;\r
460         }\r
461 };      \r
462 \r
463 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) \r
464 {\r
465         if(params.size() < 1 || params[0] != L"DECKLINK")\r
466                 return core::frame_consumer::empty();\r
467         \r
468         configuration config;\r
469                 \r
470         if(params.size() > 1)\r
471                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
472         \r
473         config.internal_key             = std::find(params.begin(), params.end(), L"INTERNAL_KEY")       != params.end();\r
474         config.low_latency              = std::find(params.begin(), params.end(), L"LOW_LATENCY")        != params.end();\r
475         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
476         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
477 \r
478         return make_safe<decklink_consumer_proxy>(config);\r
479 }\r
480 \r
481 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::ptree& ptree) \r
482 {\r
483         configuration config;\r
484 \r
485         config.internal_key                     = ptree.get("internal-key",             config.internal_key);\r
486         config.low_latency                      = ptree.get("low-latency",              config.low_latency);\r
487         config.key_only                         = ptree.get("key-only",                 config.key_only);\r
488         config.device_index                     = ptree.get("device",                   config.device_index);\r
489         config.embedded_audio           = ptree.get("embedded-audio",   config.embedded_audio);\r
490         config.base_buffer_depth        = ptree.get("buffer-depth",             config.base_buffer_depth);\r
491 \r
492         return make_safe<decklink_consumer_proxy>(config);\r
493 }\r
494 \r
495 }}\r
496 \r
497 /*\r
498 ##############################################################################\r
499 Pre-rolling\r
500 \r
501 Mail: 2011-05-09\r
502 \r
503 Yoshan\r
504 BMD Developer Support\r
505 developer@blackmagic-design.com\r
506 \r
507 -----------------------------------------------------------------------------\r
508 \r
509 Thanks for your inquiry. The minimum number of frames that you can preroll \r
510 for scheduled playback is three frames for video and four frames for audio. \r
511 As you mentioned if you preroll less frames then playback will not start or\r
512 playback will be very sporadic. From our experience with Media Express, we \r
513 recommended that at least seven frames are prerolled for smooth playback. \r
514 \r
515 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
516 There can be around 3 frames worth of latency on scheduled output.\r
517 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
518 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
519 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
520 guarantee that the provided frame will be output as soon the previous \r
521 frame output has been completed.\r
522 ################################################################################\r
523 */\r
524 \r
525 /*\r
526 ##############################################################################\r
527 Async DMA Transfer without redundant copying\r
528 \r
529 Mail: 2011-05-10\r
530 \r
531 Yoshan\r
532 BMD Developer Support\r
533 developer@blackmagic-design.com\r
534 \r
535 -----------------------------------------------------------------------------\r
536 \r
537 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
538 and providing a pointer to your video buffer when GetBytes() is called. \r
539 This may help to keep copying to a minimum. Please ensure that the pixel \r
540 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
541 have to colourspace convert which may result in additional copying.\r
542 ################################################################################\r
543 */