]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
2.1.0: Refactoring. Simplified frame handling.
[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/exception/exceptions.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 safe_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         safe_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(make_safe_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 safe_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 safe_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         std::vector<int>                                        audio_cadence_;\r
488         executor                                                        executor_;\r
489 public:\r
490 \r
491         decklink_consumer_proxy(const configuration& config)\r
492                 : config_(config)\r
493                 , executor_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
494         {\r
495                 executor_.begin_invoke([=]\r
496                 {\r
497                         ::CoInitialize(nullptr);\r
498                 });\r
499         }\r
500 \r
501         ~decklink_consumer_proxy()\r
502         {\r
503                 executor_.invoke([=]\r
504                 {\r
505                         if(consumer_)\r
506                         {\r
507                                 auto str = print();\r
508                                 consumer_.reset();\r
509                                 CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     \r
510                         }\r
511                         ::CoUninitialize();\r
512                 });\r
513         }\r
514 \r
515         // frame_consumer\r
516         \r
517         virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override\r
518         {\r
519                 executor_.invoke([=]\r
520                 {\r
521                         consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));            \r
522                         audio_cadence_ = format_desc.audio_cadence;             \r
523 \r
524                         CASPAR_LOG(info) << print() << L" Successfully Initialized.";   \r
525                 });\r
526         }\r
527         \r
528         virtual bool send(const safe_ptr<const core::data_frame>& frame) override\r
529         {\r
530                 CASPAR_VERIFY(audio_cadence_.front() == static_cast<int>(frame->audio_data().size()));\r
531                 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);\r
532 \r
533                 consumer_->send(frame);\r
534                 return true;\r
535         }\r
536         \r
537         virtual std::wstring print() const override\r
538         {\r
539                 return consumer_ ? consumer_->print() : L"[decklink_consumer]";\r
540         }               \r
541 \r
542         virtual boost::property_tree::wptree info() const override\r
543         {\r
544                 boost::property_tree::wptree info;\r
545                 info.add(L"type", L"decklink-consumer");\r
546                 info.add(L"key-only", config_.key_only);\r
547                 info.add(L"device", config_.device_index);\r
548                 info.add(L"low-latency", config_.low_latency);\r
549                 info.add(L"embedded-audio", config_.embedded_audio);\r
550                 info.add(L"low-latency", config_.low_latency);\r
551                 //info.add(L"internal-key", config_.internal_key);\r
552                 return info;\r
553         }\r
554 \r
555         virtual int buffer_depth() const override\r
556         {\r
557                 return config_.buffer_depth();\r
558         }\r
559 \r
560         virtual int index() const override\r
561         {\r
562                 return 300 + config_.device_index;\r
563         }\r
564 };      \r
565 \r
566 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) \r
567 {\r
568         if(params.size() < 1 || params[0] != L"DECKLINK")\r
569                 return core::frame_consumer::empty();\r
570         \r
571         configuration config;\r
572                 \r
573         if(params.size() > 1)\r
574                 config.device_index = boost::lexical_cast<int>(params[1]);\r
575         \r
576         if(std::find(params.begin(), params.end(), L"INTERNAL_KEY")                     != params.end())\r
577                 config.keyer = configuration::internal_keyer;\r
578         else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY")        != params.end())\r
579                 config.keyer = configuration::external_keyer;\r
580         else\r
581                 config.keyer = configuration::default_keyer;\r
582 \r
583         if(std::find(params.begin(), params.end(), L"LOW_LATENCY")       != params.end())\r
584                 config.latency = configuration::low_latency;\r
585 \r
586         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
587         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
588 \r
589         return make_safe<decklink_consumer_proxy>(config);\r
590 }\r
591 \r
592 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) \r
593 {\r
594         configuration config;\r
595 \r
596         auto keyer = ptree.get(L"keyer", L"external");\r
597         if(keyer == L"external")\r
598                 config.keyer = configuration::external_keyer;\r
599         else if(keyer == L"internal")\r
600                 config.keyer = configuration::internal_keyer;\r
601 \r
602         auto latency = ptree.get(L"latency", L"normal");\r
603         if(latency == L"low")\r
604                 config.latency = configuration::low_latency;\r
605         else if(latency == L"normal")\r
606                 config.latency = configuration::normal_latency;\r
607 \r
608         config.key_only                         = ptree.get(L"key-only",                config.key_only);\r
609         config.device_index                     = ptree.get(L"device",                  config.device_index);\r
610         config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);\r
611         config.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);\r
612 \r
613         return make_safe<decklink_consumer_proxy>(config);\r
614 }\r
615 \r
616 }}\r
617 \r
618 /*\r
619 ##############################################################################\r
620 Pre-rolling\r
621 \r
622 Mail: 2011-05-09\r
623 \r
624 Yoshan\r
625 BMD Developer Support\r
626 developer@blackmagic-design.com\r
627 \r
628 -----------------------------------------------------------------------------\r
629 \r
630 Thanks for your inquiry. The minimum number of frames that you can preroll \r
631 for scheduled playback is three frames for video and four frames for audio. \r
632 As you mentioned if you preroll less frames then playback will not start or\r
633 playback will be very sporadic. From our experience with Media Express, we \r
634 recommended that at least seven frames are prerolled for smooth playback. \r
635 \r
636 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
637 There can be around 3 frames worth of latency on scheduled output.\r
638 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
639 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
640 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
641 guarantee that the provided frame will be output as soon the previous \r
642 frame output has been completed.\r
643 ################################################################################\r
644 */\r
645 \r
646 /*\r
647 ##############################################################################\r
648 Async DMA Transfer without redundant copying\r
649 \r
650 Mail: 2011-05-10\r
651 \r
652 Yoshan\r
653 BMD Developer Support\r
654 developer@blackmagic-design.com\r
655 \r
656 -----------------------------------------------------------------------------\r
657 \r
658 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
659 and providing a pointer to your video buffer when GetBytes() is called. \r
660 This may help to keep copying to a minimum. Please ensure that the pixel \r
661 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
662 have to colourspace convert which may result in additional copying.\r
663 ################################################################################\r
664 */