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