]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
94109e912a6a6070295e2b911bf3066000e75c3c
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*\r
2 * Copyright 2013 Sveriges Television AB http://casparcg.com/\r
3 *\r
4 * This file is part of CasparCG (www.casparcg.com).\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 * Author: Robert Nagy, ronag89@gmail.com\r
20 */\r
21 \r
22 #include "../StdAfx.h"\r
23  \r
24 #include "decklink_consumer.h"\r
25 \r
26 #include "../util/util.h"\r
27 #include "../util/decklink_allocator.h"\r
28 \r
29 #include "../interop/DeckLinkAPI_h.h"\r
30 \r
31 #include <core/mixer/read_frame.h>\r
32 \r
33 #include <common/concurrency/com_context.h>\r
34 #include <common/concurrency/future_util.h>\r
35 #include <common/diagnostics/graph.h>\r
36 #include <common/exception/exceptions.h>\r
37 #include <common/exception/win32_exception.h>\r
38 #include <common/utility/assert.h>\r
39 \r
40 #include <core/parameters/parameters.h>\r
41 #include <core/consumer/frame_consumer.h>\r
42 #include <core/mixer/audio/audio_util.h>\r
43 \r
44 #include <tbb/concurrent_queue.h>\r
45 #include <tbb/cache_aligned_allocator.h>\r
46 \r
47 #include <boost/circular_buffer.hpp>\r
48 #include <boost/timer.hpp>\r
49 #include <boost/property_tree/ptree.hpp>\r
50 #include <boost/algorithm/string.hpp>\r
51 \r
52 namespace caspar { namespace decklink { \r
53 \r
54 struct key_video_context\r
55                 : public IDeckLinkVideoOutputCallback, boost::noncopyable\r
56 {\r
57         CComPtr<IDeckLink>                                                                              decklink_;\r
58         CComQIPtr<IDeckLinkOutput>                                                              output_;\r
59         CComQIPtr<IDeckLinkKeyer>                                                               keyer_;\r
60         CComQIPtr<IDeckLinkAttributes>                                                  attributes_;\r
61         CComQIPtr<IDeckLinkConfiguration>                                               configuration_;\r
62         const std::unique_ptr<thread_safe_decklink_allocator>&  allocator_;\r
63         tbb::atomic<int64_t>                                                                    current_presentation_delay_;\r
64         tbb::atomic<int64_t>                                                                    scheduled_frames_completed_;\r
65 \r
66         key_video_context(\r
67                         const configuration& config,\r
68                         const std::wstring& print,\r
69                         const std::unique_ptr<thread_safe_decklink_allocator>& allocator)\r
70                 : decklink_(get_device(config.key_device_index()))\r
71                 , output_(decklink_)\r
72                 , keyer_(decklink_)\r
73                 , attributes_(decklink_)\r
74                 , configuration_(decklink_)\r
75                 , allocator_(allocator)\r
76         {\r
77                 current_presentation_delay_ = 0;\r
78                 scheduled_frames_completed_ = 0;\r
79 \r
80                 set_latency(configuration_, config.latency, print);\r
81                 set_keyer(attributes_, keyer_, config.keyer, print);\r
82 \r
83                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
84                         BOOST_THROW_EXCEPTION(caspar_exception() \r
85                                                                         << msg_info(narrow(print) + " Failed to set key playback completion callback.")\r
86                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
87         }\r
88 \r
89         template<typename Print>\r
90         void enable_video(BMDDisplayMode display_mode, const Print& print)\r
91         {\r
92                 if (allocator_)\r
93                 {\r
94                         if (FAILED(output_->SetVideoOutputFrameMemoryAllocator(allocator_.get())))\r
95                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set key custom memory allocator."));\r
96                 }\r
97 \r
98                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
99                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable key video output."));\r
100                 \r
101                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
102                         BOOST_THROW_EXCEPTION(caspar_exception() \r
103                                                                         << msg_info(narrow(print()) + " Failed to set key playback completion callback.")\r
104                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
105         }\r
106 \r
107         virtual ~key_video_context()\r
108         {\r
109                 if (output_) \r
110                 {\r
111                         output_->StopScheduledPlayback(0, nullptr, 0);\r
112                         output_->DisableVideoOutput();\r
113                 }\r
114         }\r
115         \r
116         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
117         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
118         STDMETHOD_(ULONG, Release())                            {return 1;}\r
119 \r
120         STDMETHOD(ScheduledPlaybackHasStopped())\r
121         {\r
122                 return S_OK;\r
123         }\r
124 \r
125         STDMETHOD(ScheduledFrameCompleted(\r
126                         IDeckLinkVideoFrame* completed_frame,\r
127                         BMDOutputFrameCompletionResult result))\r
128         {\r
129                 auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);\r
130                 current_presentation_delay_ = dframe->get_age_millis();\r
131                 ++scheduled_frames_completed_;\r
132 \r
133                 // Let the fill callback keep the pace, so no scheduling here.\r
134 \r
135                 return S_OK;\r
136         }\r
137 };\r
138 \r
139 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
140 {\r
141         const int                                                       channel_index_;\r
142         const configuration                                     config_;\r
143 \r
144         std::unique_ptr<thread_safe_decklink_allocator> allocator_;\r
145         CComPtr<IDeckLink>                                      decklink_;\r
146         CComQIPtr<IDeckLinkOutput>                      output_;\r
147         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
148         CComQIPtr<IDeckLinkAttributes>          attributes_;\r
149         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
150 \r
151         tbb::spin_mutex                                         exception_mutex_;\r
152         std::exception_ptr                                      exception_;\r
153 \r
154         tbb::atomic<bool>                                       is_running_;\r
155                 \r
156         const std::wstring                                      model_name_;\r
157         const core::video_format_desc           format_desc_;\r
158         const size_t                                            buffer_size_;\r
159 \r
160         long long                                                       video_scheduled_;\r
161         long long                                                       audio_scheduled_;\r
162 \r
163         size_t                                                          preroll_count_;\r
164                 \r
165         boost::circular_buffer<std::vector<int32_t>>    audio_container_;\r
166 \r
167         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;\r
168         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;\r
169         \r
170         safe_ptr<diagnostics::graph> graph_;\r
171         boost::timer tick_timer_;\r
172         retry_task<bool> send_completion_;\r
173         reference_signal_detector reference_signal_detector_;\r
174 \r
175         tbb::atomic<int64_t>                            current_presentation_delay_;\r
176         tbb::atomic<int64_t>                            scheduled_frames_completed_;\r
177         std::unique_ptr<key_video_context>      key_context_;\r
178 \r
179 public:\r
180         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) \r
181                 : channel_index_(channel_index)\r
182                 , config_(config)\r
183                 , decklink_(get_device(config.device_index))\r
184                 , output_(decklink_)\r
185                 , keyer_(decklink_)\r
186                 , attributes_(decklink_)\r
187                 , configuration_(decklink_)\r
188                 , model_name_(get_model_name(decklink_))\r
189                 , format_desc_(format_desc)\r
190                 , buffer_size_(config.buffer_depth()) // Minimum buffer-size 3.\r
191                 , video_scheduled_(0)\r
192                 , audio_scheduled_(0)\r
193                 , preroll_count_(0)\r
194                 , audio_container_(buffer_size_+1)\r
195                 , reference_signal_detector_(output_)\r
196         {\r
197                 is_running_ = true;\r
198                 current_presentation_delay_ = 0;\r
199                 scheduled_frames_completed_ = 0;\r
200                                 \r
201                 video_frame_buffer_.set_capacity(1);\r
202 \r
203                 if (format_desc.fps > 50.0)\r
204                         // Blackmagic calls RenderAudioSamples() 50 times per second\r
205                         // regardless of video mode so we sometimes need to give them\r
206                         // samples from 2 frames in order to keep up\r
207                         audio_frame_buffer_.set_capacity(2); \r
208                 else\r
209                         audio_frame_buffer_.set_capacity(1);\r
210 \r
211                 if (config.keyer == configuration::external_separate_device_keyer)\r
212                         key_context_.reset(new key_video_context(config, print(), allocator_));\r
213 \r
214                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   \r
215                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
216                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));\r
217                 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));\r
218                 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));\r
219                 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));\r
220 \r
221                 if (key_context_)\r
222                 {\r
223                         graph_->set_color("key-offset", diagnostics::color(1.0f, 0.0f, 0.0f));\r
224                 }\r
225 \r
226                 graph_->set_text(print());\r
227                 diagnostics::register_graph(graph_);\r
228                 \r
229                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));\r
230                                 \r
231                 if(config.embedded_audio)\r
232                         enable_audio();\r
233 \r
234                 set_latency(configuration_, config.latency, print());\r
235                 set_keyer(attributes_, keyer_, config.keyer, print());\r
236                                 \r
237                 if(config.embedded_audio)               \r
238                         output_->BeginAudioPreroll();           \r
239                 \r
240                 for(size_t n = 0; n < buffer_size_; ++n)\r
241                         schedule_next_video(make_safe<core::read_frame>());\r
242 \r
243                 if(!config.embedded_audio)\r
244                         start_playback();\r
245         }\r
246 \r
247         ~decklink_consumer()\r
248         {               \r
249                 is_running_ = false;\r
250                 video_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
251                 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());\r
252 \r
253                 if(output_ != nullptr) \r
254                 {\r
255                         output_->StopScheduledPlayback(0, nullptr, 0);\r
256                         if(config_.embedded_audio)\r
257                                 output_->DisableAudioOutput();\r
258                         output_->DisableVideoOutput();\r
259                 }\r
260         }\r
261         \r
262         void enable_audio()\r
263         {\r
264                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, config_.num_out_channels(), bmdAudioOutputStreamTimestamped)))\r
265                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
266                                 \r
267                 if(FAILED(output_->SetAudioCallback(this)))\r
268                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set audio callback."));\r
269 \r
270                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
271         }\r
272 \r
273         void enable_video(BMDDisplayMode display_mode)\r
274         {\r
275                 if (config_.custom_allocator)\r
276                 {\r
277                         allocator_.reset(new thread_safe_decklink_allocator(print()));\r
278 \r
279                         if (FAILED(output_->SetVideoOutputFrameMemoryAllocator(allocator_.get())))\r
280                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not set fill custom memory allocator."));\r
281 \r
282                         CASPAR_LOG(info) << print() << L" Using custom allocator.";\r
283                 }\r
284 \r
285                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
286                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable fill video output."));\r
287                 \r
288                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
289                         BOOST_THROW_EXCEPTION(caspar_exception() \r
290                                                                         << msg_info(narrow(print()) + " Failed to set fill playback completion callback.")\r
291                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
292 \r
293                 if (key_context_)\r
294                         key_context_->enable_video(display_mode, [this]() { return print(); });\r
295         }\r
296 \r
297         void start_playback()\r
298         {\r
299                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
300                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule fill playback."));\r
301 \r
302                 if(key_context_ && FAILED(key_context_->output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
303                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule key playback."));\r
304         }\r
305         \r
306         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
307         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
308         STDMETHOD_(ULONG, Release())                            {return 1;}\r
309         \r
310         STDMETHOD(ScheduledPlaybackHasStopped())\r
311         {\r
312                 is_running_ = false;\r
313                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
314                 return S_OK;\r
315         }\r
316 \r
317         STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))\r
318         {\r
319                 win32_exception::ensure_handler_installed_for_thread("decklink-ScheduledFrameCompleted");\r
320                 if(!is_running_)\r
321                         return E_FAIL;\r
322                 \r
323                 try\r
324                 {\r
325                         auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);\r
326                         current_presentation_delay_ = dframe->get_age_millis();\r
327                         ++scheduled_frames_completed_;\r
328 \r
329                         if (key_context_)\r
330                                 graph_->set_value(\r
331                                                 "key-offset",\r
332                                                 static_cast<double>(\r
333                                                                 scheduled_frames_completed_\r
334                                                                 - key_context_->scheduled_frames_completed_)\r
335                                                 * 0.1 + 0.5);\r
336 \r
337                         if(result == bmdOutputFrameDisplayedLate)\r
338                         {\r
339                                 graph_->set_tag("late-frame");\r
340                                 video_scheduled_ += format_desc_.duration;\r
341                                 audio_scheduled_ += dframe->audio_data().size()/config_.num_out_channels();\r
342                                 //++video_scheduled_;\r
343                                 //audio_scheduled_ += format_desc_.audio_cadence[0];\r
344                                 //++audio_scheduled_;\r
345                         }\r
346                         else if(result == bmdOutputFrameDropped)\r
347                                 graph_->set_tag("dropped-frame");\r
348                         else if(result == bmdOutputFrameFlushed)\r
349                                 graph_->set_tag("flushed-frame");\r
350 \r
351                         std::shared_ptr<core::read_frame> frame;        \r
352                         video_frame_buffer_.pop(frame);\r
353                         send_completion_.try_completion();\r
354                         schedule_next_video(make_safe_ptr(frame));      \r
355                         \r
356                         unsigned long buffered;\r
357                         output_->GetBufferedVideoFrameCount(&buffered);\r
358                         graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);\r
359                 }\r
360                 catch(...)\r
361                 {\r
362                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
363                         exception_ = std::current_exception();\r
364                         return E_FAIL;\r
365                 }\r
366 \r
367                 return S_OK;\r
368         }\r
369                 \r
370         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
371         {\r
372                 win32_exception::ensure_handler_installed_for_thread("decklink-RenderAudioSamples");\r
373 \r
374                 if(!is_running_)\r
375                         return E_FAIL;\r
376                 \r
377                 try\r
378                 {       \r
379                         if(preroll)\r
380                         {\r
381                                 if(++preroll_count_ >= buffer_size_)\r
382                                 {\r
383                                         output_->EndAudioPreroll();\r
384                                         start_playback();                               \r
385                                 }\r
386                                 else\r
387                                 {\r
388                                         core::audio_buffer silent_audio(format_desc_.audio_cadence[preroll_count_ % format_desc_.audio_cadence.size()] * config_.num_out_channels(), 0);\r
389                                         auto view = core::make_multichannel_view<int32_t>(silent_audio.begin(), silent_audio.end(), config_.audio_layout, config_.num_out_channels());\r
390                                         schedule_next_audio(view);\r
391                                 }\r
392                         }\r
393                         else\r
394                         {\r
395                                 std::shared_ptr<core::read_frame> frame;\r
396 \r
397                                 while (audio_frame_buffer_.try_pop(frame))\r
398                                 {\r
399                                         send_completion_.try_completion();\r
400                                         schedule_next_audio(frame->multichannel_view());\r
401                                 }\r
402                         }\r
403 \r
404                         unsigned long buffered;\r
405                         output_->GetBufferedAudioSampleFrameCount(&buffered);\r
406                         graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0] * config_.num_out_channels() * 2));\r
407                 }\r
408                 catch(...)\r
409                 {\r
410                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
411                         exception_ = std::current_exception();\r
412                         return E_FAIL;\r
413                 }\r
414 \r
415                 return S_OK;\r
416         }\r
417 \r
418         template<typename View>\r
419         void schedule_next_audio(const View& view)\r
420         {\r
421                 const int sample_frame_count = view.num_samples();\r
422 \r
423                 if (core::needs_rearranging(\r
424                                 view, config_.audio_layout, config_.num_out_channels()))\r
425                 {\r
426                         std::vector<int32_t> resulting_audio_data;\r
427                         resulting_audio_data.resize(\r
428                                         sample_frame_count * config_.num_out_channels());\r
429 \r
430                         auto dest_view = core::make_multichannel_view<int32_t>(\r
431                                         resulting_audio_data.begin(), \r
432                                         resulting_audio_data.end(),\r
433                                         config_.audio_layout,\r
434                                         config_.num_out_channels());\r
435 \r
436                         core::rearrange_or_rearrange_and_mix(\r
437                                         view, dest_view, core::default_mix_config_repository());\r
438 \r
439                         if (config_.audio_layout.num_channels == 1) // mono\r
440                                 boost::copy(                            // duplicate L to R\r
441                                                 dest_view.channel(0),\r
442                                                 dest_view.channel(1).begin());\r
443 \r
444                         audio_container_.push_back(std::move(resulting_audio_data));\r
445                 }\r
446                 else\r
447                 {\r
448                         audio_container_.push_back(\r
449                                         std::vector<int32_t>(view.raw_begin(), view.raw_end()));\r
450                 }\r
451 \r
452                 if(FAILED(output_->ScheduleAudioSamples(\r
453                                 audio_container_.back().data(),\r
454                                 sample_frame_count,\r
455                                 audio_scheduled_,\r
456                                 format_desc_.audio_sample_rate,\r
457                                 nullptr)))\r
458                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
459 \r
460                 audio_scheduled_ += sample_frame_count;\r
461         }\r
462                         \r
463         void schedule_next_video(const safe_ptr<core::read_frame>& frame)\r
464         {\r
465                 if (key_context_)\r
466                 {\r
467                         CComPtr<IDeckLinkVideoFrame> key_frame(new decklink_frame(frame, format_desc_, true));\r
468                         if(FAILED(key_context_->output_->ScheduleVideoFrame(key_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
469                                 CASPAR_LOG(error) << print() << L" Failed to schedule key video.";\r
470                 }\r
471 \r
472                 CComPtr<IDeckLinkVideoFrame> fill_frame(new decklink_frame(frame, format_desc_, config_.key_only));\r
473                 if(FAILED(output_->ScheduleVideoFrame(fill_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
474                         CASPAR_LOG(error) << print() << L" Failed to schedule fill video.";\r
475 \r
476                 video_scheduled_ += format_desc_.duration;\r
477 \r
478                 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
479                 tick_timer_.restart();\r
480 \r
481                 reference_signal_detector_.detect_change([this]() { return print(); });\r
482         }\r
483 \r
484         boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame)\r
485         {\r
486                 {\r
487                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
488                         if(exception_ != nullptr)\r
489                                 std::rethrow_exception(exception_);\r
490                 }\r
491 \r
492                 if(!is_running_)\r
493                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));\r
494 \r
495                 bool audio_ready = !config_.embedded_audio;\r
496                 bool video_ready = false;\r
497 \r
498                 auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>\r
499                 {\r
500                         if (!audio_ready)\r
501                                 audio_ready = audio_frame_buffer_.try_push(frame);\r
502 \r
503                         if (!video_ready)\r
504                                 video_ready = video_frame_buffer_.try_push(frame);\r
505 \r
506                         if (audio_ready && video_ready)\r
507                                 return true;\r
508                         else\r
509                                 return boost::optional<bool>();\r
510                 };\r
511                 \r
512                 if (enqueue_task())\r
513                         return wrap_as_future(true);\r
514 \r
515                 send_completion_.set_task(enqueue_task);\r
516 \r
517                 return send_completion_.get_future();\r
518         }\r
519         \r
520         std::wstring print() const\r
521         {\r
522                 if (config_.keyer == configuration::external_separate_device_keyer)\r
523                         return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
524                                 boost::lexical_cast<std::wstring>(config_.device_index) +\r
525                                 L"&&" +\r
526                                 boost::lexical_cast<std::wstring>(config_.key_device_index()) +\r
527                                 L"|" +\r
528                                 format_desc_.name + L"]";\r
529                 else\r
530                         return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
531                                 boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
532         }\r
533 };\r
534 \r
535 struct decklink_consumer_proxy : public core::frame_consumer\r
536 {\r
537         const configuration                             config_;\r
538         com_context<decklink_consumer>  context_;\r
539         std::vector<size_t>                             audio_cadence_;\r
540         core::video_format_desc                 format_desc_;\r
541 public:\r
542 \r
543         decklink_consumer_proxy(const configuration& config)\r
544                 : config_(config)\r
545                 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
546         {\r
547         }\r
548 \r
549         ~decklink_consumer_proxy()\r
550         {\r
551                 if(context_)\r
552                 {\r
553                         auto str = print();\r
554                         context_.reset();\r
555                         CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     \r
556                 }\r
557         }\r
558 \r
559         // frame_consumer\r
560         \r
561         virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override\r
562         {\r
563                 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index);});                \r
564                 audio_cadence_ = format_desc.audio_cadence;             \r
565                 format_desc_ = format_desc;\r
566 \r
567                 CASPAR_LOG(info) << print() << L" Successfully Initialized.";   \r
568         }\r
569         \r
570         virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
571         {\r
572                 CASPAR_VERIFY(audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()));\r
573                 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);\r
574 \r
575                 return context_->send(frame);\r
576         }\r
577         \r
578         virtual std::wstring print() const override\r
579         {\r
580                 return context_ ? context_->print() : L"[decklink_consumer]";\r
581         }               \r
582 \r
583         virtual boost::property_tree::wptree info() const override\r
584         {\r
585                 boost::property_tree::wptree info;\r
586                 info.add(L"type", L"decklink-consumer");\r
587                 info.add(L"key-only", config_.key_only);\r
588                 info.add(L"device", config_.device_index);\r
589 \r
590                 if (config_.keyer == configuration::external_separate_device_keyer)\r
591                 {\r
592                         info.add(L"key-device", config_.key_device_index());\r
593                 }\r
594 \r
595                 info.add(L"low-latency", config_.latency == configuration::low_latency);\r
596                 info.add(L"embedded-audio", config_.embedded_audio);\r
597                 info.add(L"presentation-frame-age", presentation_frame_age_millis());\r
598                 //info.add(L"internal-key", config_.internal_key);\r
599                 return info;\r
600         }\r
601 \r
602         virtual size_t buffer_depth() const override\r
603         {\r
604                 return config_.buffer_depth();\r
605         }\r
606 \r
607         virtual int index() const override\r
608         {\r
609                 return 300 + config_.device_index;\r
610         }\r
611 \r
612         virtual int64_t presentation_frame_age_millis() const\r
613         {\r
614                 return context_ ? context_->current_presentation_delay_ : 0;\r
615         }\r
616 };      \r
617 \r
618 safe_ptr<core::frame_consumer> create_consumer(const core::parameters& params) \r
619 {\r
620         if(params.size() < 1 || params[0] != L"DECKLINK")\r
621                 return core::frame_consumer::empty();\r
622         \r
623         configuration config;\r
624                 \r
625         if(params.size() > 1)\r
626                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
627         \r
628         if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())\r
629                 config.keyer = configuration::internal_keyer;\r
630         else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())\r
631                 config.keyer = configuration::external_keyer;\r
632         else if(std::find(params.begin(), params.end(), L"EXTERNAL_SEPARATE_DEVICE_KEY") != params.end())\r
633                 config.keyer = configuration::external_separate_device_keyer;\r
634         else\r
635                 config.keyer = configuration::default_keyer;\r
636 \r
637         if(std::find(params.begin(), params.end(), L"LOW_LATENCY")       != params.end())\r
638                 config.latency = configuration::low_latency;\r
639 \r
640         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
641         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
642         config.audio_layout             = core::default_channel_layout_repository().get_by_name(\r
643                         params.get(L"CHANNEL_LAYOUT", L"STEREO"));\r
644 \r
645         return make_safe<decklink_consumer_proxy>(config);\r
646 }\r
647 \r
648 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) \r
649 {\r
650         configuration config;\r
651 \r
652         auto keyer = ptree.get(L"keyer", L"external");\r
653         if(keyer == L"external")\r
654                 config.keyer = configuration::external_keyer;\r
655         else if(keyer == L"internal")\r
656                 config.keyer = configuration::internal_keyer;\r
657         else if(keyer == L"external_separate_device")\r
658                 config.keyer = configuration::external_separate_device_keyer;\r
659 \r
660         auto latency = ptree.get(L"latency", L"normal");\r
661         if(latency == L"low")\r
662                 config.latency = configuration::low_latency;\r
663         else if(latency == L"normal")\r
664                 config.latency = configuration::normal_latency;\r
665 \r
666         config.key_only                         = ptree.get(L"key-only",                        config.key_only);\r
667         config.device_index                     = ptree.get(L"device",                          config.device_index);\r
668         config.key_device_idx           = ptree.get(L"key-device",                      config.key_device_idx);\r
669         config.embedded_audio           = ptree.get(L"embedded-audio",          config.embedded_audio);\r
670         config.base_buffer_depth        = ptree.get(L"buffer-depth",            config.base_buffer_depth);\r
671         config.custom_allocator         = ptree.get(L"custom-allocator",        config.custom_allocator);\r
672         config.audio_layout =\r
673                 core::default_channel_layout_repository().get_by_name(\r
674                                 boost::to_upper_copy(ptree.get(L"channel-layout", L"STEREO")));\r
675 \r
676         return make_safe<decklink_consumer_proxy>(config);\r
677 }\r
678 \r
679 }}\r
680 \r
681 /*\r
682 ##############################################################################\r
683 Pre-rolling\r
684 \r
685 Mail: 2011-05-09\r
686 \r
687 Yoshan\r
688 BMD Developer Support\r
689 developer@blackmagic-design.com\r
690 \r
691 -----------------------------------------------------------------------------\r
692 \r
693 Thanks for your inquiry. The minimum number of frames that you can preroll \r
694 for scheduled playback is three frames for video and four frames for audio. \r
695 As you mentioned if you preroll less frames then playback will not start or\r
696 playback will be very sporadic. From our experience with Media Express, we \r
697 recommended that at least seven frames are prerolled for smooth playback. \r
698 \r
699 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
700 There can be around 3 frames worth of latency on scheduled output.\r
701 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
702 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
703 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
704 guarantee that the provided frame will be output as soon the previous \r
705 frame output has been completed.\r
706 ################################################################################\r
707 */\r
708 \r
709 /*\r
710 ##############################################################################\r
711 Async DMA Transfer without redundant copying\r
712 \r
713 Mail: 2011-05-10\r
714 \r
715 Yoshan\r
716 BMD Developer Support\r
717 developer@blackmagic-design.com\r
718 \r
719 -----------------------------------------------------------------------------\r
720 \r
721 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
722 and providing a pointer to your video buffer when GetBytes() is called. \r
723 This may help to keep copying to a minimum. Please ensure that the pixel \r
724 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
725 have to colourspace convert which may result in additional copying.\r
726 ################################################################################\r
727 */