]> git.sesse.net Git - casparcg/blob - modules/decklink/consumer/decklink_consumer.cpp
04b9f194cf557fcec2b29888ea5261b86f80aca5
[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/diagnostics/graph.h>\r
34 #include <common/exception/exceptions.h>\r
35 #include <common/memory/memcpy.h>\r
36 #include <common/memory/memclr.h>\r
37 #include <common/memory/memshfl.h>\r
38 \r
39 #include <core/consumer/frame_consumer.h>\r
40 \r
41 #include <tbb/concurrent_queue.h>\r
42 #include <tbb/cache_aligned_allocator.h>\r
43 \r
44 #include <boost/circular_buffer.hpp>\r
45 #include <boost/timer.hpp>\r
46 #include <boost/property_tree/ptree.hpp>\r
47 \r
48 namespace caspar { namespace decklink { \r
49         \r
50 struct configuration\r
51 {\r
52         size_t  device_index;\r
53         bool    embedded_audio;\r
54         bool    internal_key;\r
55         bool    low_latency;\r
56         bool    key_only;\r
57         size_t  base_buffer_depth;\r
58         size_t  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                 , buffer_depth(base_buffer_depth + (low_latency ? 0 : 1) + (embedded_audio ? 1 : 0)){}\r
68 };\r
69 \r
70 class decklink_frame : public IDeckLinkVideoFrame\r
71 {\r
72         tbb::atomic<int>                                                                                        ref_count_;\r
73         std::shared_ptr<core::read_frame>                                                       frame_;\r
74         const core::video_format_desc                                                           format_desc_;\r
75 \r
76         const bool                                                                                                      key_only_;\r
77         std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> data_;\r
78 public:\r
79         decklink_frame(const safe_ptr<core::read_frame>& frame, const core::video_format_desc& format_desc, bool key_only)\r
80                 : frame_(frame)\r
81                 , format_desc_(format_desc)\r
82                 , key_only_(key_only)\r
83         {\r
84                 ref_count_ = 0;\r
85         }\r
86         \r
87         // IUnknown\r
88 \r
89         STDMETHOD (QueryInterface(REFIID, LPVOID*))             \r
90         {\r
91                 return E_NOINTERFACE;\r
92         }\r
93         \r
94         STDMETHOD_(ULONG,                       AddRef())                       \r
95         {\r
96                 return ++ref_count_;\r
97         }\r
98 \r
99         STDMETHOD_(ULONG,                       Release())                      \r
100         {\r
101                 if(--ref_count_ == 0)\r
102                         delete this;\r
103                 return ref_count_;\r
104         }\r
105 \r
106         // IDecklinkVideoFrame\r
107 \r
108         STDMETHOD_(long,                        GetWidth())                     {return format_desc_.width;}        \r
109     STDMETHOD_(long,                    GetHeight())            {return format_desc_.height;}        \r
110     STDMETHOD_(long,                    GetRowBytes())          {return format_desc_.width*4;}        \r
111         STDMETHOD_(BMDPixelFormat,      GetPixelFormat())       {return bmdFormat8BitBGRA;}        \r
112     STDMETHOD_(BMDFrameFlags,   GetFlags())                     {return bmdFrameFlagDefault;}\r
113         \r
114     STDMETHOD(GetBytes(void** buffer))\r
115         {\r
116                 try\r
117                 {\r
118                         if(static_cast<size_t>(frame_->image_data().size()) != format_desc_.size)\r
119                         {\r
120                                 data_.resize(format_desc_.size, 0);\r
121                                 *buffer = data_.data();\r
122                         }\r
123                         else if(key_only_)\r
124                         {\r
125                                 if(data_.empty())\r
126                                 {\r
127                                         data_.resize(frame_->image_data().size());\r
128                                         fast_memshfl(data_.data(), frame_->image_data().begin(), frame_->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
129                                         frame_.reset();\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 size_t                                            buffer_size_;\r
174 \r
175         long long                                                       video_scheduled_;\r
176         long long                                                       audio_scheduled_;\r
177 \r
178         size_t                                                          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(size_t 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(narrow(print()) + " Could not enable audio output."));\r
291                                 \r
292                 if(FAILED(output_->SetAudioCallback(this)))\r
293                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(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(narrow(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(narrow(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(narrow(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                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
358                         exception_ = std::current_exception();\r
359                         return E_FAIL;\r
360                 }\r
361 \r
362                 return S_OK;\r
363         }\r
364                 \r
365         STDMETHOD(RenderAudioSamples(BOOL preroll))\r
366         {\r
367                 if(!is_running_)\r
368                         return E_FAIL;\r
369                 \r
370                 try\r
371                 {       \r
372                         if(preroll)\r
373                         {\r
374                                 if(++preroll_count_ >= buffer_size_)\r
375                                 {\r
376                                         output_->EndAudioPreroll();\r
377                                         start_playback();                               \r
378                                 }\r
379                                 else\r
380                                         schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()], 0));    \r
381                         }\r
382                         else\r
383                         {\r
384                                 std::shared_ptr<core::read_frame> frame;\r
385                                 audio_frame_buffer_.pop(frame);\r
386                                 schedule_next_audio(frame->audio_data());\r
387                         }\r
388 \r
389                         unsigned long buffered;\r
390                         output_->GetBufferedAudioSampleFrameCount(&buffered);\r
391                         graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0]*2));\r
392                 }\r
393                 catch(...)\r
394                 {\r
395                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
396                         exception_ = std::current_exception();\r
397                         return E_FAIL;\r
398                 }\r
399 \r
400                 return S_OK;\r
401         }\r
402 \r
403         template<typename T>\r
404         void schedule_next_audio(const T& audio_data)\r
405         {\r
406                 const int sample_frame_count = audio_data.size()/format_desc_.audio_channels;\r
407 \r
408                 audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));\r
409 \r
410                 if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))\r
411                         CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
412 \r
413                 audio_scheduled_ += sample_frame_count;\r
414         }\r
415                         \r
416         void schedule_next_video(const safe_ptr<core::read_frame>& frame)\r
417         {\r
418                 CComPtr<IDeckLinkVideoFrame> frame2(new decklink_frame(frame, format_desc_, config_.key_only));\r
419                 if(FAILED(output_->ScheduleVideoFrame(frame2, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))\r
420                         CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
421 \r
422                 video_scheduled_ += format_desc_.duration;\r
423 \r
424                 graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);\r
425                 tick_timer_.restart();\r
426         }\r
427 \r
428         void send(const safe_ptr<core::read_frame>& frame)\r
429         {\r
430                 {\r
431                         tbb::spin_mutex::scoped_lock lock(exception_mutex_);\r
432                         if(exception_ != nullptr)\r
433                                 std::rethrow_exception(exception_);\r
434                 }\r
435 \r
436                 if(!is_running_)\r
437                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Is not running."));\r
438                 \r
439                 if(config_.embedded_audio)\r
440                         audio_frame_buffer_.push(frame);        \r
441                 video_frame_buffer_.push(frame);        \r
442         }\r
443         \r
444         std::wstring print() const\r
445         {\r
446                 return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +\r
447                         boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";\r
448         }\r
449 };\r
450 \r
451 struct decklink_consumer_proxy : public core::frame_consumer\r
452 {\r
453         const configuration                             config_;\r
454         com_context<decklink_consumer>  context_;\r
455         std::vector<size_t>                             audio_cadence_;\r
456 public:\r
457 \r
458         decklink_consumer_proxy(const configuration& config)\r
459                 : config_(config)\r
460                 , context_(L"decklink_consumer[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
461         {\r
462         }\r
463 \r
464         ~decklink_consumer_proxy()\r
465         {\r
466                 if(context_)\r
467                 {\r
468                         auto str = print();\r
469                         context_.reset();\r
470                         CASPAR_LOG(info) << str << L" Successfully Uninitialized.";     \r
471                 }\r
472         }\r
473 \r
474         // frame_consumer\r
475         \r
476         virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override\r
477         {\r
478                 context_.reset([&]{return new decklink_consumer(config_, format_desc, channel_index);});                \r
479                 audio_cadence_ = format_desc.audio_cadence;             \r
480 \r
481                 CASPAR_LOG(info) << print() << L" Successfully Initialized.";   \r
482         }\r
483         \r
484         virtual bool send(const safe_ptr<core::read_frame>& frame) override\r
485         {\r
486                 CASPAR_VERIFY(audio_cadence_.front() == static_cast<size_t>(frame->audio_data().size()));\r
487                 boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);\r
488 \r
489                 context_->send(frame);\r
490                 return true;\r
491         }\r
492         \r
493         virtual std::wstring print() const override\r
494         {\r
495                 return context_ ? context_->print() : L"[decklink_consumer]";\r
496         }               \r
497 \r
498         virtual boost::property_tree::wptree info() const override\r
499         {\r
500                 boost::property_tree::wptree info;\r
501                 info.add(L"type", L"decklink-consumer");\r
502                 info.add(L"key-only", config_.key_only);\r
503                 info.add(L"device", config_.device_index);\r
504                 info.add(L"low-latency", config_.low_latency);\r
505                 info.add(L"embedded-audio", config_.embedded_audio);\r
506                 info.add(L"low-latency", config_.low_latency);\r
507                 info.add(L"internal-key", config_.internal_key);\r
508                 return info;\r
509         }\r
510 \r
511         virtual size_t buffer_depth() const override\r
512         {\r
513                 return config_.buffer_depth;\r
514         }\r
515 \r
516         virtual int index() const override\r
517         {\r
518                 return 300 + config_.device_index;\r
519         }\r
520 };      \r
521 \r
522 safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params) \r
523 {\r
524         if(params.size() < 1 || params[0] != L"DECKLINK")\r
525                 return core::frame_consumer::empty();\r
526         \r
527         configuration config;\r
528                 \r
529         if(params.size() > 1)\r
530                 config.device_index = lexical_cast_or_default<int>(params[1], config.device_index);\r
531         \r
532         config.internal_key             = std::find(params.begin(), params.end(), L"INTERNAL_KEY")       != params.end();\r
533         config.low_latency              = std::find(params.begin(), params.end(), L"LOW_LATENCY")        != params.end();\r
534         config.embedded_audio   = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
535         config.key_only                 = std::find(params.begin(), params.end(), L"KEY_ONLY")           != params.end();\r
536 \r
537         return make_safe<decklink_consumer_proxy>(config);\r
538 }\r
539 \r
540 safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) \r
541 {\r
542         configuration config;\r
543 \r
544         config.internal_key                     = ptree.get(L"internal-key",    config.internal_key);\r
545         config.low_latency                      = ptree.get(L"low-latency",             config.low_latency);\r
546         config.key_only                         = ptree.get(L"key-only",                config.key_only);\r
547         config.device_index                     = ptree.get(L"device",                  config.device_index);\r
548         config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);\r
549         config.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);\r
550 \r
551         return make_safe<decklink_consumer_proxy>(config);\r
552 }\r
553 \r
554 }}\r
555 \r
556 /*\r
557 ##############################################################################\r
558 Pre-rolling\r
559 \r
560 Mail: 2011-05-09\r
561 \r
562 Yoshan\r
563 BMD Developer Support\r
564 developer@blackmagic-design.com\r
565 \r
566 -----------------------------------------------------------------------------\r
567 \r
568 Thanks for your inquiry. The minimum number of frames that you can preroll \r
569 for scheduled playback is three frames for video and four frames for audio. \r
570 As you mentioned if you preroll less frames then playback will not start or\r
571 playback will be very sporadic. From our experience with Media Express, we \r
572 recommended that at least seven frames are prerolled for smooth playback. \r
573 \r
574 Regarding the bmdDeckLinkConfigLowLatencyVideoOutput flag:\r
575 There can be around 3 frames worth of latency on scheduled output.\r
576 When the bmdDeckLinkConfigLowLatencyVideoOutput flag is used this latency is\r
577 reduced  or removed for scheduled playback. If the DisplayVideoFrameSync() \r
578 method is used, the bmdDeckLinkConfigLowLatencyVideoOutput setting will \r
579 guarantee that the provided frame will be output as soon the previous \r
580 frame output has been completed.\r
581 ################################################################################\r
582 */\r
583 \r
584 /*\r
585 ##############################################################################\r
586 Async DMA Transfer without redundant copying\r
587 \r
588 Mail: 2011-05-10\r
589 \r
590 Yoshan\r
591 BMD Developer Support\r
592 developer@blackmagic-design.com\r
593 \r
594 -----------------------------------------------------------------------------\r
595 \r
596 Thanks for your inquiry. You could try subclassing IDeckLinkMutableVideoFrame \r
597 and providing a pointer to your video buffer when GetBytes() is called. \r
598 This may help to keep copying to a minimum. Please ensure that the pixel \r
599 format is in bmdFormat10BitYUV, otherwise the DeckLink API / driver will \r
600 have to colourspace convert which may result in additional copying.\r
601 ################################################################################\r
602 */