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