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