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