]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
2.1.0: -bluefish_consumer+decklink_consumer: Removed reduntant audio cadence check.
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
1 /*\r
2 * Copyright (c) 2011 Sveriges Television AB <info@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 \r
28 #include "../interop/DeckLinkAPI_h.h"\r
29 \r
30 #include <core/frame/data_frame.h>\r
31 #include <core/mixer/audio/audio_mixer.h>\r
32 \r
33 #include <common/concurrency/executor.h>\r
34 #include <common/concurrency/lock.h>\r
35 #include <common/diagnostics/graph.h>\r
36 #include <common/except.h>\r
37 #include <common/memory/memshfl.h>\r
38 \r
39 #include <core/consumer/frame_consumer.h>\r
40 \r
41 #include <tbb/concurrent_queue.h>\r
42 #include <tbb/cache_aligned_allocator.h>\r
43 \r
44 #include <common/assert.h>\r
45 #include <boost/lexical_cast.hpp>\r
46 #include <boost/circular_buffer.hpp>\r
47 #include <boost/timer.hpp>\r
48 #include <boost/property_tree/ptree.hpp>\r
49 \r
50 namespace caspar { namespace decklink { \r
51         \r
52 struct configuration\r
53 {\r
54         enum keyer_t\r
55         {\r
56                 internal_keyer,\r
57                 external_keyer,\r
58                 default_keyer\r
59         };\r
60 \r
61         enum latency_t\r
62         {\r
63                 low_latency,\r
64                 normal_latency,\r
65                 default_latency\r
66         };\r
67 \r
68         int                     device_index;\r
69         bool            embedded_audio;\r
70         keyer_t         keyer;\r
71         latency_t       latency;\r
72         bool            key_only;\r
73         int                     base_buffer_depth;\r
74         \r
75         configuration()\r
76                 : device_index(1)\r
77                 , embedded_audio(false)\r
78                 , keyer(default_keyer)\r
79                 , latency(default_latency)\r
80                 , key_only(false)\r
81                 , base_buffer_depth(3)\r
82         {\r
83         }\r
84         \r
85         int buffer_depth() const\r
86         {\r
87                 return base_buffer_depth + (latency == low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);\r
88         }\r
89 };\r
90 \r
91 class decklink_frame : public IDeckLinkVideoFrame\r
92 {\r
93         tbb::atomic<int>                                                                                        ref_count_;\r
94         std::shared_ptr<const core::data_frame>                                                         frame_;\r
95         const core::video_format_desc                                                           format_desc_;\r
96 \r
97         const bool                                                                                                      key_only_;\r
98         std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> data_;\r
99 public:\r
100         decklink_frame(const spl::shared_ptr<const core::data_frame>& frame, const core::video_format_desc& format_desc, bool key_only)\r
101                 : frame_(frame)\r
102                 , format_desc_(format_desc)\r
103                 , key_only_(key_only)\r
104         {\r
105                 ref_count_ = 0;\r
106         }\r
107         \r
108         // IUnknown\r
109 \r
110         STDMETHOD (QueryInterface(REFIID, LPVOID*))             \r
111         {\r
112                 return E_NOINTERFACE;\r
113         }\r
114         \r
115         STDMETHOD_(ULONG,                       AddRef())                       \r
116         {\r
117                 return ++ref_count_;\r
118         }\r
119 \r
120         STDMETHOD_(ULONG,                       Release())                      \r
121         {\r
122                 if(--ref_count_ == 0)\r
123                         delete this;\r
124                 return ref_count_;\r
125         }\r
126 \r
127         // IDecklinkVideoFrame\r
128 \r
129         STDMETHOD_(long,                        GetWidth())                     {return format_desc_.width;}        \r
130     STDMETHOD_(long,                    GetHeight())            {return format_desc_.height;}        \r
131     STDMETHOD_(long,                    GetRowBytes())          {return format_desc_.width*4;}        \r
132         STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        \r
133     STDMETHOD_(BMDFrameFlags,   GetFlags())                     {return bmdFrameFlagDefault;}\r
134         \r
135     STDMETHOD(GetBytes(void** buffer))\r
136         {\r
137                 try\r
138                 {\r
139                         if(static_cast<int>(frame_->image_data().size()) != format_desc_.size)\r
140                         {\r
141                                 data_.resize(format_desc_.size, 0);\r
142                                 *buffer = data_.data();\r
143                         }\r
144                         else if(key_only_)\r
145                         {\r
146                                 if(data_.empty())\r
147                                 {\r
148                                         data_.resize(frame_->image_data().size());\r
149                                         aligned_memshfl(data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
150                                 }\r
151                                 *buffer = data_.data();\r
152                         }\r
153                         else\r
154                                 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());\r
155                 }\r
156                 catch(...)\r
157                 {\r
158                         CASPAR_LOG_CURRENT_EXCEPTION();\r
159                         return E_FAIL;\r
160                 }\r
161 \r
162                 return S_OK;\r
163         }\r
164         \r
165     STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
166     STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                 {return S_FALSE;}\r
167 \r
168         // decklink_frame       \r
169 \r
170         const core::audio_buffer& audio_data()\r
171         {\r
172                 return frame_->audio_data();\r
173         }\r
174 };\r
175 \r
176 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
177 {               \r
178         const int                                                       channel_index_;\r
179         const configuration                                     config_;\r
180 \r
181         CComPtr<IDeckLink>                                      decklink_;\r
182         CComQIPtr<IDeckLinkOutput>                      output_;\r
183         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
184         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
185         CComQIPtr<IDeckLinkAttributes>          attributes_;\r
186 \r
187         tbb::spin_mutex                                         exception_mutex_;\r
188         std::exception_ptr                                      exception_;\r
189 \r
190         tbb::atomic<bool>                                       is_running_;\r
191                 \r
192         const std::wstring                                      model_name_;\r
193         const core::video_format_desc           format_desc_;\r
194         const int                                                       buffer_size_;\r
195 \r
196         long long                                                       video_scheduled_;\r
197         long long                                                       audio_scheduled_;\r
198 \r
199         int                                                                     preroll_count_;\r
200                 \r
201         boost::circular_buffer<std::vector<int32_t>>    audio_container_;\r
202 \r
203         tbb::concurrent_bounded_queue<std::shared_ptr<const core::data_frame>> video_frame_buffer_;\r
204         tbb::concurrent_bounded_queue<std::shared_ptr<const core::data_frame>> audio_frame_buffer_;\r
205         \r
206         spl::shared_ptr<diagnostics::graph> graph_;\r
207         boost::timer tick_timer_;\r
208 \r
209 public:\r
210         decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) \r
211                 : channel_index_(channel_index)\r
212                 , config_(config)\r
213                 , decklink_(get_device(config.device_index))\r
214                 , output_(decklink_)\r
215                 , configuration_(decklink_)\r
216                 , keyer_(decklink_)\r
217                 , attributes_(decklink_)\r
218                 , model_name_(get_model_name(decklink_))\r
219                 , format_desc_(format_desc)\r
220                 , buffer_size_(config.buffer_depth()) // Minimum buffer-size 3.\r
221                 , video_scheduled_(0)\r
222                 , audio_scheduled_(0)\r
223                 , preroll_count_(0)\r
224                 , audio_container_(buffer_size_+1)\r
225         {\r
226                 is_running_ = true;\r
227                                 \r
228                 video_frame_buffer_.set_capacity(1);\r
229                 audio_frame_buffer_.set_capacity(1);\r
230 \r
231                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   \r
232                 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
233                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));\r
234                 graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));\r
235                 graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));\r
236                 graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));\r
237                 graph_->set_text(print());\r
238                 diagnostics::register_graph(graph_);\r
239                 \r
240                 enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));\r
241                                 \r
242                 if(config.embedded_audio)\r
243                         enable_audio();\r
244                 \r
245                 set_latency(config.latency);                            \r
246                 set_keyer(config.keyer);\r
247                                 \r
248                 if(config.embedded_audio)               \r
249                         output_->BeginAudioPreroll();           \r
250                 \r
251                 for(int n = 0; n < buffer_size_; ++n)\r
252                         schedule_next_video(core::data_frame::empty());\r
253 \r
254                 if(!config.embedded_audio)\r
255                         start_playback();\r
256         }\r
257 \r
258         ~decklink_consumer()\r
259         {               \r
260                 is_running_ = false;\r
261                 video_frame_buffer_.try_push(core::data_frame::empty());\r
262                 audio_frame_buffer_.try_push(core::data_frame::empty());\r
263 \r
264                 if(output_ != nullptr) \r
265                 {\r
266                         output_->StopScheduledPlayback(0, nullptr, 0);\r
267                         if(config_.embedded_audio)\r
268                                 output_->DisableAudioOutput();\r
269                         output_->DisableVideoOutput();\r
270                 }\r
271         }\r
272                         \r
273         void set_latency(configuration::latency_t latency)\r
274         {               \r
275                 if(latency == configuration::low_latency)\r
276                 {\r
277                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
278                         CASPAR_LOG(info) << print() << L" Enabled low-latency mode.";\r
279                 }\r
280                 else if(latency == configuration::normal_latency)\r
281                 {                       \r
282                         configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
283                         CASPAR_LOG(info) << print() << L" Disabled low-latency mode.";\r
284                 }\r
285         }\r
286 \r
287         void set_keyer(configuration::keyer_t keyer)\r
288         {\r
289                 if(keyer == configuration::internal_keyer) \r
290                 {\r
291                         BOOL value = true;\r
292                         if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)\r
293                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";   \r
294                         else if(FAILED(keyer_->Enable(FALSE)))                  \r
295                                 CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   \r
296                         else if(FAILED(keyer_->SetLevel(255)))                  \r
297                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
298                         else\r
299                                 CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             \r
300                 }\r
301                 else if(keyer == configuration::external_keyer)\r
302                 {\r
303                         BOOL value = true;\r
304                         if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)\r
305                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
306                         else if(FAILED(keyer_->Enable(TRUE)))                   \r
307                                 CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   \r
308                         else if(FAILED(keyer_->SetLevel(255)))                  \r
309                                 CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
310                         else\r
311                                 CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     \r
312                 }\r
313         }\r
314         \r
315         void enable_audio()\r
316         {\r
317                 if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
318                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable audio output."));\r
319                                 \r
320                 if(FAILED(output_->SetAudioCallback(this)))\r
321                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not set audio callback."));\r
322 \r
323                 CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
324         }\r
325 \r
326         void enable_video(BMDDisplayMode display_mode)\r
327         {\r
328                 if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) \r
329                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable video output."));\r
330                 \r
331                 if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
332                         BOOST_THROW_EXCEPTION(caspar_exception() \r
333                                                                         << msg_info(u8(print()) + " Failed to set playback completion callback.")\r
334                                                                         << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));\r
335         }\r
336 \r
337         void start_playback()\r
338         {\r
339                 if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) \r
340                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule playback."));\r
341         }\r
342         \r
343         STDMETHOD (QueryInterface(REFIID, LPVOID*))     {return E_NOINTERFACE;}\r
344         STDMETHOD_(ULONG, AddRef())                                     {return 1;}\r
345         STDMETHOD_(ULONG, Release())                            {return 1;}\r
346         \r
347         STDMETHOD(ScheduledPlaybackHasStopped())\r
348         {\r
349                 is_running_ = false;\r
350                 CASPAR_LOG(info) << print() << L" Scheduled playback has stopped.";\r
351                 return S_OK;\r
352         }\r
353 \r
354         STDMETHOD(ScheduledFrameCompleted(IDeckLinkVideoFrame* completed_frame, BMDOutputFrameCompletionResult result))\r
355         {\r
356                 if(!is_running_)\r
357                         return E_FAIL;\r
358                 \r
359                 try\r
360                 {\r
361                         if(result == bmdOutputFrameDisplayedLate)\r
362                         {\r
363                                 graph_->set_tag("late-frame");\r
364                                 video_scheduled_ += format_desc_.duration;\r
365                                 audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;\r
366                                 //++video_scheduled_;\r
367                                 //audio_scheduled_ += format_desc_.audio_cadence[0];\r
368                                 //++audio_scheduled_;\r
369                         }\r
370                         else if(result == bmdOutputFrameDropped)\r
371                                 graph_->set_tag("dropped-frame");\r
372                         else if(result == bmdOutputFrameFlushed)\r
373                                 graph_->set_tag("flushed-frame");\r
374 \r
375                         std::shared_ptr<const core::data_frame> frame;  \r
376                         video_frame_buffer_.pop(frame);                                 \r
377                         schedule_next_video(spl::make_shared_ptr(frame));       \r
378                         \r
379                         unsigned long buffered;\r
380                         output_->GetBufferedVideoFrameCount(&buffered);\r
381                         graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);\r
382                 }\r
383                 catch(...)\r
384                 {\r
385                         lock(exception_mutex_, [&]\r
386                         {\r
387                                 exception_ = std::current_exception();\r
388                         });\r
389                         return E_FAIL;\r
390                 }\r
391 \r
392                 return S_OK;\r
393         }\r
394                 \r
395         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
396         {\r
397                 if(!is_running_)\r
398                         return E_FAIL;\r
399                 \r
400                 try\r
401                 {       \r
402                         if(preroll)\r
403                         {\r
404                                 if(++preroll_count_ >= buffer_size_)\r
405                                 {\r
406                                         output_->EndAudioPreroll();\r
407                                         start_playback();                               \r
408                                 }\r
409                                 else\r
410                                         schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()], 0));    \r
411                         }\r
412                         else\r
413                         {\r
414                                 std::shared_ptr<const core::data_frame> frame;\r
415                                 audio_frame_buffer_.pop(frame);\r
416                                 schedule_next_audio(frame->audio_data());\r
417                         }\r
418 \r
419                         unsigned long buffered;\r
420                         output_->GetBufferedAudioSampleFrameCount(&buffered);\r
421                         graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0]*2));\r
422                 }\r
423                 catch(...)\r
424                 {\r
425                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
426                         exception_ = std::current_exception();\r
427                         return E_FAIL;\r
428                 }\r
429 \r
430                 return S_OK;\r
431         }\r
432 \r
433         template<typename T>\r
434         void schedule_next_audio(const T& audio_data)\r
435         {\r
436                 auto sample_frame_count = static_cast<int>(audio_data.size()/format_desc_.audio_channels);\r
437 \r
438                 audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));\r
439 \r
440                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))\r
441                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
442 \r
443                 audio_scheduled_ += sample_frame_count;\r
444         }\r
445                         \r
446         void schedule_next_video(const spl::shared_ptr<const core::data_frame>& frame)\r
447         {\r
448                 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));\r
449                 if(FAILED(output_->ScheduleVideoFrame(frame2, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
450                         CASPAR_LOG(error) << print() << L" Failed to schedule 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 \r
458         void send(const spl::shared_ptr<const core::data_frame>& frame)\r
459         {\r
460                 auto exception = lock(exception_mutex_, [&]\r
461                 {\r
462                         return exception_;\r
463                 });\r
464 \r
465                 if(exception != nullptr)\r
466                         std::rethrow_exception(exception);              \r
467 \r
468                 if(!is_running_)\r
469                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Is not running."));\r
470                 \r
471                 if(config_.embedded_audio)\r
472                         audio_frame_buffer_.push(frame);        \r
473                 video_frame_buffer_.push(frame);        \r
474         }\r
475         \r
476         std::wstring print() const\r
477         {\r
478                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
479                         boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
480         }\r
481 };\r
482 \r
483 struct decklink_consumer_proxy : public core::frame_consumer\r
484 {\r
485         const configuration                                     config_;\r
486         std::unique_ptr<decklink_consumer>      consumer_;\r
487         executor                                                        executor_;\r
488 public:\r
489 \r
490         decklink_consumer_proxy(const configuration& config)\r
491                 : config_(config)\r
492                 , executor_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
493         {\r
494                 executor_.begin_invoke([=]\r
495                 {\r
496                         ::CoInitialize(nullptr);\r
497                 });\r
498         }\r
499 \r
500         ~decklink_consumer_proxy()\r
501         {\r
502                 executor_.invoke([=]\r
503                 {\r
504                         ::CoUninitialize();\r
505                 });\r
506         }\r
507 \r
508         // frame_consumer\r
509         \r
510         virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override\r
511         {\r
512                 executor_.invoke([=]\r
513                 {\r
514                         consumer_.reset();\r
515                         consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));                    \r
516                 });\r
517         }\r
518         \r
519         virtual bool send(const spl::shared_ptr<const core::data_frame>& frame) override\r
520         {\r
521                 consumer_->send(frame);\r
522                 return true;\r
523         }\r
524         \r
525         virtual std::wstring print() const override\r
526         {\r
527                 return consumer_ ? consumer_->print() : L"[decklink_consumer]";\r
528         }               \r
529 \r
530         virtual std::wstring name() const override\r
531         {\r
532                 return L"decklink";\r
533         }\r
534 \r
535         virtual boost::property_tree::wptree info() const override\r
536         {\r
537                 boost::property_tree::wptree info;\r
538                 info.add(L"type", L"decklink");\r
539                 info.add(L"key-only", config_.key_only);\r
540                 info.add(L"device", config_.device_index);\r
541                 info.add(L"low-latency", config_.low_latency);\r
542                 info.add(L"embedded-audio", config_.embedded_audio);\r
543                 info.add(L"low-latency", config_.low_latency);\r
544                 //info.add(L"internal-key", config_.internal_key);\r
545                 return info;\r
546         }\r
547 \r
548         virtual int buffer_depth() const override\r
549         {\r
550                 return config_.buffer_depth();\r
551         }\r
552 \r
553         virtual int index() const override\r
554         {\r
555                 return 300 + config_.device_index;\r
556         }\r
557 };      \r
558 \r
559 spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) \r
560 {\r
561         if(params.size() < 1 || params[0] != L"DECKLINK")\r
562                 return core::frame_consumer::empty();\r
563         \r
564         configuration config;\r
565                 \r
566         if(params.size() > 1)\r
567                 config.device_index = boost::lexical_cast<int>(params[1]);\r
568         \r
569         if(std::find(params.begin(), params.end(), L"INTERNAL_KEY")                     != params.end())\r
570                 config.keyer = configuration::internal_keyer;\r
571         else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY")        != params.end())\r
572                 config.keyer = configuration::external_keyer;\r
573         else\r
574                 config.keyer = configuration::default_keyer;\r
575 \r
576         if(std::find(params.begin(), params.end(), L"LOW_LATENCY")       != params.end())\r
577                 config.latency = configuration::low_latency;\r
578 \r
579         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
580         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
581 \r
582         return spl::make_shared<decklink_consumer_proxy>(config);\r
583 }\r
584 \r
585 spl::shared_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) \r
586 {\r
587         configuration config;\r
588 \r
589         auto keyer = ptree.get(L"keyer", L"external");\r
590         if(keyer == L"external")\r
591                 config.keyer = configuration::external_keyer;\r
592         else if(keyer == L"internal")\r
593                 config.keyer = configuration::internal_keyer;\r
594 \r
595         auto latency = ptree.get(L"latency", L"normal");\r
596         if(latency == L"low")\r
597                 config.latency = configuration::low_latency;\r
598         else if(latency == L"normal")\r
599                 config.latency = configuration::normal_latency;\r
600 \r
601         config.key_only                         = ptree.get(L"key-only",                config.key_only);\r
602         config.device_index                     = ptree.get(L"device",                  config.device_index);\r
603         config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);\r
604         config.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);\r
605 \r
606         return spl::make_shared<decklink_consumer_proxy>(config);\r
607 }\r
608 \r
609 }}\r
610 \r
611 /*\r
612 ##############################################################################\r
613 Pre-rolling\r
614 \r
615 Mail: 2011-05-09\r
616 \r
617 Yoshan\r
618 BMD Developer Support\r
619 developer@blackmagic-design.com\r
620 \r
621 -----------------------------------------------------------------------------\r
622 \r
623 Thanks for your inquiry. The minimum number of frames that you can preroll \r
624 for scheduled playback is three frames for video and four frames for audio. \r
625 As you mentioned if you preroll less frames then playback will not start or\r
626 playback will be very sporadic. From our experience with Media Express, we \r
627 recommended that at least seven frames are prerolled for smooth playback. \r
628 \r
629 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
630 There can be around 3 frames worth of latency on scheduled output.\r
631 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
632 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
633 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
634 guarantee that the provided frame will be output as soon the previous \r
635 frame output has been completed.\r
636 ################################################################################\r
637 */\r
638 \r
639 /*\r
640 ##############################################################################\r
641 Async DMA Transfer without redundant copying\r
642 \r
643 Mail: 2011-05-10\r
644 \r
645 Yoshan\r
646 BMD Developer Support\r
647 developer@blackmagic-design.com\r
648 \r
649 -----------------------------------------------------------------------------\r
650 \r
651 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
652 and providing a pointer to your video buffer when GetBytes() is called. \r
653 This may help to keep copying to a minimum. Please ensure that the pixel \r
654 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
655 have to colourspace convert which may result in additional copying.\r
656 ################################################################################\r
657 */