]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
4e764ec217033c427be40a320b3917b3d8350ae6
[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/mixer/read_frame.h>\r
31 \r
32 #include <common/concurrency/com_context.h>\r
33 #include <common/concurrency/future_util.h>\r
34 #include <common/diagnostics/graph.h>\r
35 #include <common/exception/exceptions.h>\r
36 #include <common/memory/memcpy.h>\r
37 #include <common/memory/memclr.h>\r
38 #include <common/memory/memshfl.h>\r
39 \r
40 #include <core/consumer/frame_consumer.h>\r
41 \r
42 #include <tbb/concurrent_queue.h>\r
43 #include <tbb/cache_aligned_allocator.h>\r
44 \r
45 #include <boost/circular_buffer.hpp>\r
46 #include <boost/timer.hpp>\r
47 #include <boost/property_tree/ptree.hpp>\r
48 \r
49 namespace caspar { namespace decklink { \r
50         \r
51 struct configuration\r
52 {\r
53         enum keyer_t\r
54         {\r
55                 internal_keyer,\r
56                 external_keyer,\r
57                 default_keyer\r
58         };\r
59 \r
60         enum latency_t\r
61         {\r
62                 low_latency,\r
63                 normal_latency,\r
64                 default_latency\r
65         };\r
66 \r
67         size_t          device_index;\r
68         bool            embedded_audio;\r
69         keyer_t         keyer;\r
70         latency_t       latency;\r
71         bool            key_only;\r
72         size_t          base_buffer_depth;\r
73         \r
74         configuration()\r
75                 : device_index(1)\r
76                 , embedded_audio(false)\r
77                 , keyer(default_keyer)\r
78                 , latency(default_latency)\r
79                 , key_only(false)\r
80                 , base_buffer_depth(3)\r
81         {\r
82         }\r
83         \r
84         size_t buffer_depth() const\r
85         {\r
86                 return base_buffer_depth + (latency == low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);\r
87         }\r
88 };\r
89 \r
90 class decklink_frame : public IDeckLinkVideoFrame\r
91 {\r
92         tbb::atomic<int>                                                                                        ref_count_;\r
93         std::shared_ptr<core::read_frame>                                                       frame_;\r
94         const core::video_format_desc                                                           format_desc_;\r
95 \r
96         const bool                                                                                                      key_only_;\r
97         std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> data_;\r
98 public:\r
99         decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)\r
100                 : frame_(frame)\r
101                 , format_desc_(format_desc)\r
102                 , key_only_(key_only)\r
103         {\r
104                 ref_count_ = 0;\r
105         }\r
106         \r
107         // IUnknown\r
108 \r
109         STDMETHOD (QueryInterface(REFIID, LPVOID*))             \r
110         {\r
111                 return E_NOINTERFACE;\r
112         }\r
113         \r
114         STDMETHOD_(ULONG,                       AddRef())                       \r
115         {\r
116                 return ++ref_count_;\r
117         }\r
118 \r
119         STDMETHOD_(ULONG,                       Release())                      \r
120         {\r
121                 if(--ref_count_ == 0)\r
122                         delete this;\r
123                 return ref_count_;\r
124         }\r
125 \r
126         // IDecklinkVideoFrame\r
127 \r
128         STDMETHOD_(long,                        GetWidth())                     {return format_desc_.width;}        \r
129     STDMETHOD_(long,                    GetHeight())            {return format_desc_.height;}        \r
130     STDMETHOD_(long,                    GetRowBytes())          {return format_desc_.width*4;}        \r
131         STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        \r
132     STDMETHOD_(BMDFrameFlags,   GetFlags())                     {return bmdFrameFlagDefault;}\r
133         \r
134     STDMETHOD(GetBytes(void** buffer))\r
135         {\r
136                 try\r
137                 {\r
138                         if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
139                         {\r
140                                 data_.resize(format_desc_.size, 0);\r
141                                 *buffer = data_.data();\r
142                         }\r
143                         else if(key_only_)\r
144                         {\r
145                                 if(data_.empty())\r
146                                 {\r
147                                         data_.resize(frame_->image_data().size());\r
148                                         fast_memshfl(data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
149                                 }\r
150                                 *buffer = data_.data();\r
151                         }\r
152                         else\r
153                                 *buffer = const_cast<uint8_t*>(frame_->image_data().begin());\r
154                 }\r
155                 catch(...)\r
156                 {\r
157                         CASPAR_LOG_CURRENT_EXCEPTION();\r
158                         return E_FAIL;\r
159                 }\r
160 \r
161                 return S_OK;\r
162         }\r
163         \r
164     STDMETHOD(GetTimecode(BMDTimecodeFormat format, IDeckLinkTimecode** timecode)){return S_FALSE;}        \r
165     STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary))                 {return S_FALSE;}\r
166 \r
167         // decklink_frame       \r
168 \r
169         const boost::iterator_range<const int32_t*> audio_data()\r
170         {\r
171                 return frame_->audio_data();\r
172         }\r
173 };\r
174 \r
175 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
176 {               \r
177         const int                                                       channel_index_;\r
178         const configuration                                     config_;\r
179 \r
180         CComPtr<IDeckLink>                                      decklink_;\r
181         CComQIPtr<IDeckLinkOutput>                      output_;\r
182         CComQIPtr<IDeckLinkConfiguration>       configuration_;\r
183         CComQIPtr<IDeckLinkKeyer>                       keyer_;\r
184         CComQIPtr<IDeckLinkAttributes>          attributes_;\r
185 \r
186         tbb::spin_mutex                                         exception_mutex_;\r
187         std::exception_ptr                                      exception_;\r
188 \r
189         tbb::atomic<bool>                                       is_running_;\r
190                 \r
191         const std::wstring                                      model_name_;\r
192         const core::video_format_desc           format_desc_;\r
193         const size_t                                            buffer_size_;\r
194 \r
195         long long                                                       video_scheduled_;\r
196         long long                                                       audio_scheduled_;\r
197 \r
198         size_t                                                          preroll_count_;\r
199                 \r
200         boost::circular_buffer<std::vector<int32_t>>    audio_container_;\r
201 \r
202         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> video_frame_buffer_;\r
203         tbb::concurrent_bounded_queue<std::shared_ptr<core::read_frame>> audio_frame_buffer_;\r
204         \r
205         safe_ptr<diagnostics::graph> graph_;\r
206         boost::timer tick_timer_;\r
207         retry_task<bool> send_completion_;\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(size_t n = 0; n < buffer_size_; ++n)\r
252                         schedule_next_video(make_safe<core::read_frame>());\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(std::make_shared<core::read_frame>());\r
262                 audio_frame_buffer_.try_push(std::make_shared<core::read_frame>());\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(narrow(print()) + " Could not enable audio output."));\r
319                                 \r
320                 if(FAILED(output_->SetAudioCallback(this)))\r
321                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(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(narrow(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(narrow(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(narrow(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<core::read_frame> frame;        \r
376                         video_frame_buffer_.pop(frame);\r
377                         send_completion_.try_completion();\r
378                         schedule_next_video(make_safe_ptr(frame));      \r
379                         \r
380                         unsigned long buffered;\r
381                         output_->GetBufferedVideoFrameCount(&buffered);\r
382                         graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);\r
383                 }\r
384                 catch(...)\r
385                 {\r
386                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
387                         exception_ = std::current_exception();\r
388                         return E_FAIL;\r
389                 }\r
390 \r
391                 return S_OK;\r
392         }\r
393                 \r
394         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
395         {\r
396                 if(!is_running_)\r
397                         return E_FAIL;\r
398                 \r
399                 try\r
400                 {       \r
401                         if(preroll)\r
402                         {\r
403                                 if(++preroll_count_ >= buffer_size_)\r
404                                 {\r
405                                         output_->EndAudioPreroll();\r
406                                         start_playback();                               \r
407                                 }\r
408                                 else\r
409                                         schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()], 0));    \r
410                         }\r
411                         else\r
412                         {\r
413                                 std::shared_ptr<core::read_frame> frame;\r
414                                 audio_frame_buffer_.pop(frame);\r
415                                 send_completion_.try_completion();\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                 const int sample_frame_count = 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<core::read_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         boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame)\r
459         {\r
460                 {\r
461                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
462                         if(exception_ != nullptr)\r
463                                 std::rethrow_exception(exception_);\r
464                 }\r
465 \r
466                 if(!is_running_)\r
467                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));\r
468 \r
469                 bool audio_ready = !config_.embedded_audio;\r
470                 bool video_ready = false;\r
471 \r
472                 auto enqueue_task = [audio_ready, video_ready, frame, this]() mutable -> boost::optional<bool>\r
473                 {\r
474                         if (!audio_ready)\r
475                                 audio_ready = audio_frame_buffer_.try_push(frame);\r
476 \r
477                         if (!video_ready)\r
478                                 video_ready = video_frame_buffer_.try_push(frame);\r
479 \r
480                         if (audio_ready && video_ready)\r
481                                 return true;\r
482                         else\r
483                                 return boost::optional<bool>();\r
484                 };\r
485                 \r
486                 if (enqueue_task())\r
487                         return wrap_as_future(true);\r
488 \r
489                 send_completion_.set_task(enqueue_task);\r
490 \r
491                 return send_completion_.get_future();\r
492         }\r
493         \r
494         std::wstring print() const\r
495         {\r
496                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
497                         boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
498         }\r
499 };\r
500 \r
501 struct decklink_consumer_proxy : public core::frame_consumer\r
502 {\r
503         const configuration                             config_;\r
504         com_context<decklink_consumer>  context_;\r
505         std::vector<size_t>                             audio_cadence_;\r
506 public:\r
507 \r
508         decklink_consumer_proxy(const configuration& config)\r
509                 : config_(config)\r
510                 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
511         {\r
512         }\r
513 \r
514         ~decklink_consumer_proxy()\r
515         {\r
516                 if(context_)\r
517                 {\r
518                         auto str = print();\r
519                         context_.reset();\r
520                         CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     \r
521                 }\r
522         }\r
523 \r
524         // frame_consumer\r
525         \r
526         virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override\r
527         {\r
528                 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index);});                \r
529                 audio_cadence_ = format_desc.audio_cadence;             \r
530 \r
531                 CASPAR_LOG(info) << print() << L" Successfully Initialized.";   \r
532         }\r
533         \r
534         virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
535         {\r
536                 CASPAR_VERIFY(audio_cadence_.front() == static_cast<size_t>(frame->audio_data().size()));\r
537                 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);\r
538 \r
539                 return context_->send(frame);\r
540         }\r
541         \r
542         virtual std::wstring print() const override\r
543         {\r
544                 return context_ ? context_->print() : L"[decklink_consumer]";\r
545         }               \r
546 \r
547         virtual boost::property_tree::wptree info() const override\r
548         {\r
549                 boost::property_tree::wptree info;\r
550                 info.add(L"type", L"decklink-consumer");\r
551                 info.add(L"key-only", config_.key_only);\r
552                 info.add(L"device", config_.device_index);\r
553                 info.add(L"low-latency", config_.low_latency);\r
554                 info.add(L"embedded-audio", config_.embedded_audio);\r
555                 info.add(L"low-latency", config_.low_latency);\r
556                 //info.add(L"internal-key", config_.internal_key);\r
557                 return info;\r
558         }\r
559 \r
560         virtual size_t buffer_depth() const override\r
561         {\r
562                 return config_.buffer_depth();\r
563         }\r
564 \r
565         virtual int index() const override\r
566         {\r
567                 return 300 + config_.device_index;\r
568         }\r
569 };      \r
570 \r
571 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) \r
572 {\r
573         if(params.size() < 1 || params[0] != L"DECKLINK")\r
574                 return core::frame_consumer::empty();\r
575         \r
576         configuration config;\r
577                 \r
578         if(params.size() > 1)\r
579                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
580         \r
581         if(std::find(params.begin(), params.end(), L"INTERNAL_KEY")                     != params.end())\r
582                 config.keyer = configuration::internal_keyer;\r
583         else if(std::find(params.begin(), params.end(), L"EXTERNAL_KEY")        != params.end())\r
584                 config.keyer = configuration::external_keyer;\r
585         else\r
586                 config.keyer = configuration::default_keyer;\r
587 \r
588         if(std::find(params.begin(), params.end(), L"LOW_LATENCY")       != params.end())\r
589                 config.latency = configuration::low_latency;\r
590 \r
591         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
592         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
593 \r
594         return make_safe<decklink_consumer_proxy>(config);\r
595 }\r
596 \r
597 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) \r
598 {\r
599         configuration config;\r
600 \r
601         auto keyer = ptree.get(L"keyer", L"external");\r
602         if(keyer == L"external")\r
603                 config.keyer = configuration::external_keyer;\r
604         else if(keyer == L"internal")\r
605                 config.keyer = configuration::internal_keyer;\r
606 \r
607         auto latency = ptree.get(L"latency", L"normal");\r
608         if(latency == L"low")\r
609                 config.latency = configuration::low_latency;\r
610         else if(latency == L"normal")\r
611                 config.latency = configuration::normal_latency;\r
612 \r
613         config.key_only                         = ptree.get(L"key-only",                config.key_only);\r
614         config.device_index                     = ptree.get(L"device",                  config.device_index);\r
615         config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);\r
616         config.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);\r
617 \r
618         return make_safe<decklink_consumer_proxy>(config);\r
619 }\r
620 \r
621 }}\r
622 \r
623 /*\r
624 ##############################################################################\r
625 Pre-rolling\r
626 \r
627 Mail: 2011-05-09\r
628 \r
629 Yoshan\r
630 BMD Developer Support\r
631 developer@blackmagic-design.com\r
632 \r
633 -----------------------------------------------------------------------------\r
634 \r
635 Thanks for your inquiry. The minimum number of frames that you can preroll \r
636 for scheduled playback is three frames for video and four frames for audio. \r
637 As you mentioned if you preroll less frames then playback will not start or\r
638 playback will be very sporadic. From our experience with Media Express, we \r
639 recommended that at least seven frames are prerolled for smooth playback. \r
640 \r
641 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
642 There can be around 3 frames worth of latency on scheduled output.\r
643 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
644 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
645 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
646 guarantee that the provided frame will be output as soon the previous \r
647 frame output has been completed.\r
648 ################################################################################\r
649 */\r
650 \r
651 /*\r
652 ##############################################################################\r
653 Async DMA Transfer without redundant copying\r
654 \r
655 Mail: 2011-05-10\r
656 \r
657 Yoshan\r
658 BMD Developer Support\r
659 developer@blackmagic-design.com\r
660 \r
661 -----------------------------------------------------------------------------\r
662 \r
663 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
664 and providing a pointer to your video buffer when GetBytes() is called. \r
665 This may help to keep copying to a minimum. Please ensure that the pixel \r
666 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
667 have to colourspace convert which may result in additional copying.\r
668 ################################################################################\r
669 */