]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
f723bcd154e431e2b76310a2aa7bfc94bbe8ae0d
[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, tbb::cache_aligned_allocator<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                 audio_container_.push_back(core::get_rearranged_and_mixed(\r
424                                 view,\r
425                                 config_.audio_layout,\r
426                                 config_.num_out_channels()));\r
427 \r
428                 if(FAILED(output_->ScheduleAudioSamples(\r
429                                 audio_container_.back().data(),\r
430                                 sample_frame_count,\r
431                                 audio_scheduled_,\r
432                                 format_desc_.audio_sample_rate,\r
433                                 nullptr)))\r
434                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
435 \r
436                 audio_scheduled_ += sample_frame_count;\r
437         }\r
438                         \r
439         void schedule_next_video(const safe_ptr<core::read_frame>& frame)\r
440         {\r
441                 if (key_context_)\r
442                 {\r
443                         CComPtr<IDeckLinkVideoFrame> key_frame(new decklink_frame(frame, format_desc_, true));\r
444                         if(FAILED(key_context_->output_->ScheduleVideoFrame(key_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
445                                 CASPAR_LOG(error) << print() << L" Failed to schedule key video.";\r
446                 }\r
447 \r
448                 CComPtr<IDeckLinkVideoFrame> fill_frame(new decklink_frame(frame, format_desc_, config_.key_only));\r
449                 if(FAILED(output_->ScheduleVideoFrame(fill_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
450                         CASPAR_LOG(error) << print() << L" Failed to schedule fill video.";\r
451 \r
452                 video_scheduled_ += format_desc_.duration;\r
453 \r
454                 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
455                 tick_timer_.restart();\r
456 \r
457                 reference_signal_detector_.detect_change([this]() { return print(); });\r
458         }\r
459 \r
460         boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame)\r
461         {\r
462                 {\r
463                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
464                         if(exception_ != nullptr)\r
465                                 std::rethrow_exception(exception_);\r
466                 }\r
467 \r
468                 if(!is_running_)\r
469                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));\r
470 \r
471                 bool audio_ready = !config_.embedded_audio;\r
472                 bool video_ready = false;\r
473 \r
474                 auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>\r
475                 {\r
476                         if (!audio_ready)\r
477                                 audio_ready = audio_frame_buffer_.try_push(frame);\r
478 \r
479                         if (!video_ready)\r
480                                 video_ready = video_frame_buffer_.try_push(frame);\r
481 \r
482                         if (audio_ready && video_ready)\r
483                                 return true;\r
484                         else\r
485                                 return boost::optional<bool>();\r
486                 };\r
487                 \r
488                 if (enqueue_task())\r
489                         return wrap_as_future(true);\r
490 \r
491                 send_completion_.set_task(enqueue_task);\r
492 \r
493                 return send_completion_.get_future();\r
494         }\r
495         \r
496         std::wstring print() const\r
497         {\r
498                 if (config_.keyer == configuration::external_separate_device_keyer)\r
499                         return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
500                                 boost::lexical_cast<std::wstring>(config_.device_index) +\r
501                                 L"&&" +\r
502                                 boost::lexical_cast<std::wstring>(config_.key_device_index()) +\r
503                                 L"|" +\r
504                                 format_desc_.name + L"]";\r
505                 else\r
506                         return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
507                                 boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
508         }\r
509 };\r
510 \r
511 struct decklink_consumer_proxy : public core::frame_consumer\r
512 {\r
513         const configuration                             config_;\r
514         com_context<decklink_consumer>  context_;\r
515         std::vector<size_t>                             audio_cadence_;\r
516         core::video_format_desc                 format_desc_;\r
517 public:\r
518 \r
519         decklink_consumer_proxy(const configuration& config)\r
520                 : config_(config)\r
521                 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
522         {\r
523         }\r
524 \r
525         ~decklink_consumer_proxy()\r
526         {\r
527                 if(context_)\r
528                 {\r
529                         auto str = print();\r
530                         context_.reset();\r
531                         CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     \r
532                 }\r
533         }\r
534 \r
535         // frame_consumer\r
536         \r
537         virtual void initialize(\r
538                         const core::video_format_desc& format_desc,\r
539                         const core::channel_layout& audio_channel_layout,\r
540                         int channel_index) override\r
541         {\r
542                 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index);});                \r
543                 audio_cadence_ = format_desc.audio_cadence;             \r
544                 format_desc_ = format_desc;\r
545 \r
546                 CASPAR_LOG(info) << print() << L" Successfully Initialized.";   \r
547         }\r
548         \r
549         virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
550         {\r
551                 CASPAR_VERIFY(audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()));\r
552                 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);\r
553 \r
554                 return context_->send(frame);\r
555         }\r
556         \r
557         virtual std::wstring print() const override\r
558         {\r
559                 return context_ ? context_->print() : L"[decklink_consumer]";\r
560         }               \r
561 \r
562         virtual boost::property_tree::wptree info() const override\r
563         {\r
564                 boost::property_tree::wptree info;\r
565                 info.add(L"type", L"decklink-consumer");\r
566                 info.add(L"key-only", config_.key_only);\r
567                 info.add(L"device", config_.device_index);\r
568 \r
569                 if (config_.keyer == configuration::external_separate_device_keyer)\r
570                 {\r
571                         info.add(L"key-device", config_.key_device_index());\r
572                 }\r
573 \r
574                 info.add(L"low-latency", config_.latency == configuration::low_latency);\r
575                 info.add(L"embedded-audio", config_.embedded_audio);\r
576                 info.add(L"presentation-frame-age", presentation_frame_age_millis());\r
577                 //info.add(L"internal-key", config_.internal_key);\r
578                 return info;\r
579         }\r
580 \r
581         virtual size_t buffer_depth() const override\r
582         {\r
583                 return config_.buffer_depth();\r
584         }\r
585 \r
586         virtual int index() const override\r
587         {\r
588                 return 300 + config_.device_index;\r
589         }\r
590 \r
591         virtual int64_t presentation_frame_age_millis() const\r
592         {\r
593                 return context_ ? context_->current_presentation_delay_ : 0;\r
594         }\r
595 };      \r
596 \r
597 safe_ptr<core::frame_consumer> create_consumer(const core::parameters& params) \r
598 {\r
599         if(params.size() < 1 || params[0] != L"DECKLINK")\r
600                 return core::frame_consumer::empty();\r
601         \r
602         configuration config;\r
603                 \r
604         if(params.size() > 1)\r
605                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
606         \r
607         if(std::find(params.begin(), params.end(), L"INTERNAL_KEY") != params.end())\r
608                 config.keyer = configuration::internal_keyer;\r
609         else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY") != params.end())\r
610                 config.keyer = configuration::external_keyer;\r
611         else if(std::find(params.begin(), params.end(), L"EXTERNAL_SEPARATE_DEVICE_KEY") != params.end())\r
612                 config.keyer = configuration::external_separate_device_keyer;\r
613         else\r
614                 config.keyer = configuration::default_keyer;\r
615 \r
616         if(std::find(params.begin(), params.end(), L"LOW_LATENCY")       != params.end())\r
617                 config.latency = configuration::low_latency;\r
618 \r
619         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
620         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
621         config.audio_layout             = core::default_channel_layout_repository().get_by_name(\r
622                         params.get(L"CHANNEL_LAYOUT", L"STEREO"));\r
623 \r
624         return make_safe<decklink_consumer_proxy>(config);\r
625 }\r
626 \r
627 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) \r
628 {\r
629         configuration config;\r
630 \r
631         auto keyer = ptree.get(L"keyer", L"external");\r
632         if(keyer == L"external")\r
633                 config.keyer = configuration::external_keyer;\r
634         else if(keyer == L"internal")\r
635                 config.keyer = configuration::internal_keyer;\r
636         else if(keyer == L"external_separate_device")\r
637                 config.keyer = configuration::external_separate_device_keyer;\r
638 \r
639         auto latency = ptree.get(L"latency", L"normal");\r
640         if(latency == L"low")\r
641                 config.latency = configuration::low_latency;\r
642         else if(latency == L"normal")\r
643                 config.latency = configuration::normal_latency;\r
644 \r
645         config.key_only                         = ptree.get(L"key-only",                        config.key_only);\r
646         config.device_index                     = ptree.get(L"device",                          config.device_index);\r
647         config.key_device_idx           = ptree.get(L"key-device",                      config.key_device_idx);\r
648         config.embedded_audio           = ptree.get(L"embedded-audio",          config.embedded_audio);\r
649         config.base_buffer_depth        = ptree.get(L"buffer-depth",            config.base_buffer_depth);\r
650         config.custom_allocator         = ptree.get(L"custom-allocator",        config.custom_allocator);\r
651         config.audio_layout =\r
652                 core::default_channel_layout_repository().get_by_name(\r
653                                 boost::to_upper_copy(ptree.get(L"channel-layout", L"STEREO")));\r
654 \r
655         return make_safe<decklink_consumer_proxy>(config);\r
656 }\r
657 \r
658 }}\r
659 \r
660 /*\r
661 ##############################################################################\r
662 Pre-rolling\r
663 \r
664 Mail: 2011-05-09\r
665 \r
666 Yoshan\r
667 BMD Developer Support\r
668 developer@blackmagic-design.com\r
669 \r
670 -----------------------------------------------------------------------------\r
671 \r
672 Thanks for your inquiry. The minimum number of frames that you can preroll \r
673 for scheduled playback is three frames for video and four frames for audio. \r
674 As you mentioned if you preroll less frames then playback will not start or\r
675 playback will be very sporadic. From our experience with Media Express, we \r
676 recommended that at least seven frames are prerolled for smooth playback. \r
677 \r
678 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
679 There can be around 3 frames worth of latency on scheduled output.\r
680 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
681 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
682 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
683 guarantee that the provided frame will be output as soon the previous \r
684 frame output has been completed.\r
685 ################################################################################\r
686 */\r
687 \r
688 /*\r
689 ##############################################################################\r
690 Async DMA Transfer without redundant copying\r
691 \r
692 Mail: 2011-05-10\r
693 \r
694 Yoshan\r
695 BMD Developer Support\r
696 developer@blackmagic-design.com\r
697 \r
698 -----------------------------------------------------------------------------\r
699 \r
700 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
701 and providing a pointer to your video buffer when GetBytes() is called. \r
702 This may help to keep copying to a minimum. Please ensure that the pixel \r
703 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
704 have to colourspace convert which may result in additional copying.\r
705 ################################################################################\r
706 */